diff --git a/core/composer.json b/core/composer.json
index 3109649..c7ca429 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -111,6 +111,7 @@
         "drupal/migrate_drupal_ui": "self.version",
         "drupal/node": "self.version",
         "drupal/options": "self.version",
+        "drupal/outside_in": "self.version",
         "drupal/page_cache": "self.version",
         "drupal/path": "self.version",
         "drupal/quickedit": "self.version",
diff --git a/core/modules/outside_in/css/outside_in.base.css b/core/modules/outside_in/css/outside_in.base.css
new file mode 100644
index 0000000..e7e60f0
--- /dev/null
+++ b/core/modules/outside_in/css/outside_in.base.css
@@ -0,0 +1,184 @@
+/**
+ * @file
+ * Resets for Outside-In module.
+ */
+
+/**
+ * Resets
+ * inside the wrapper override font styling the page provides, this may look a bit extreme
+ * but any module may place any new form element in this tray therefore CSS from any
+ * front-end-theme needs to be overridden
+ */
+#offcanvas abbr,
+#offcanvas acronym,
+#offcanvas address,
+#offcanvas article,
+#offcanvas aside,
+#offcanvas audio,
+#offcanvas b,
+#offcanvas caption,
+#offcanvas center,
+#offcanvas cite,
+#offcanvas code,
+#offcanvas dd,
+#offcanvas del,
+#offcanvas details,
+#offcanvas dfn,
+#offcanvas dialog,
+#offcanvas div,
+#offcanvas dl,
+#offcanvas dt,
+#offcanvas em,
+#offcanvas embed,
+#offcanvas fieldset,
+#offcanvas figcaption,
+#offcanvas figure,
+#offcanvas font,
+#offcanvas footer,
+#offcanvas form,
+#offcanvas h1,
+#offcanvas h2,
+#offcanvas h3,
+#offcanvas h4,
+#offcanvas h5,
+#offcanvas h6,
+#offcanvas header,
+#offcanvas hgroup,
+#offcanvas i,
+#offcanvas img,
+#offcanvas ins,
+#offcanvas kbd,
+#offcanvas label,
+#offcanvas legend,
+#offcanvas li,
+#offcanvas mark,
+#offcanvas menu,
+#offcanvas meter,
+#offcanvas nav,
+#offcanvas object,
+#offcanvas ol,
+#offcanvas output,
+#offcanvas p,
+#offcanvas pre,
+#offcanvas progress,
+#offcanvas q,
+#offcanvas rp,
+#offcanvas rt,
+#offcanvas ruby,
+#offcanvas s,
+#offcanvas samp,
+#offcanvas section,
+#offcanvas small,
+#offcanvas span,
+#offcanvas strike,
+#offcanvas strong,
+#offcanvas sub,
+#offcanvas summary,
+#offcanvas sup,
+#offcanvas table,
+#offcanvas tbody,
+#offcanvas td,
+#offcanvas tfoot,
+#offcanvas th,
+#offcanvas thead,
+#offcanvas time,
+#offcanvas tr,
+#offcanvas tt,
+#offcanvas u,
+#offcanvas ul,
+#offcanvas var,
+#offcanvas video {
+  font-family: 'Lucida Grande','Lucida Sans Unicode','Liberation Sans',sans-serif;
+  color: #eee;
+  font-size: 13px;
+  font-weight: normal;
+  font-style: normal;
+  line-height: 1.5em;
+  text-transform: none;
+  text-align: left; /* LTR */
+  text-indent: 0;
+  padding:0;
+}
+/* Reset font family rules from formalize to match Drupal (seven theme) */
+#offcanvas ::-webkit-validation-bubble-message,
+#offcanvas optgroup,
+#offcanvas .ie6_input,
+#offcanvas * html textarea,
+#offcanvas * html select,
+#offcanvas textarea,
+#offcanvas select,
+#offcanvas button,
+#offcanvas a.button,
+#offcanvas input[type="date"],
+#offcanvas input[type="datetime"],
+#offcanvas input[type="datetime-local"],
+#offcanvas input[type="email"],
+#offcanvas input[type="month"],
+#offcanvas input[type="number"],
+#offcanvas input[type="password"],
+#offcanvas input[type="search"],
+#offcanvas input[type="tel"],
+#offcanvas input[type="text"],
+#offcanvas input[type="time"],
+#offcanvas input[type="url"],
+#offcanvas input[type="week"],
+#offcanvas input[type="reset"],
+#offcanvas input[type="submit"],
+#offcanvas input[type="button"]  {
+  font-family: 'Lucida Grande','Lucida Sans Unicode','Liberation Sans',sans-serif;
+}
+/* Reset the link color to one that stands out on dark background */
+#offcanvas a {
+  color: #33aaff;
+  text-decoration: none;
+}
+/* Reset to sensible vertical rhythm */
+#offcanvas .form-item,
+#offcanvas details,
+#offcanvas button,
+.form-type-checkbox,
+.form-type-radio {
+  margin-top: 0;
+  margin-bottom: 1.25em;
+  padding-right: .5em; /* LTR */
+  display: inline-block;
+}
+/* Re-set defaults for fieldsets */
+#offcanvas .fieldset-wrapper {
+  margin: 0;
+  padding: 0;
+  border: 0;
+}
+/* Override formalize label styling */
+#offcanvas label {
+  font-size: 85%;
+  font-weight: bold;
+  line-height: 1.5em;
+}
+/* Override formalize button styling */
+#offcanvas button,
+#offcanvas .button,
+#offcanvas input[type="button"],
+#offcanvas input[type="reset"] {
+  font-weight: normal;
+  text-shadow: none;
+  width: auto;
+  max-width: 50%;
+  color: #fff;
+  border-radius: 30px;
+  border: none;
+  background-color: #777;
+  background-image: none;
+  background-image: none;
+}
+ /* Remove browser and formalize focus effects for dark background */
+#offcanvas input:focus,
+#offcanvas button:focus,
+#offcanvas a.button:focus,
+#offcanvas select:focus,
+#offcanvas textarea:focus {
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+  outline: none;
+}
diff --git a/core/modules/outside_in/css/outside_in.module.css b/core/modules/outside_in/css/outside_in.module.css
new file mode 100644
index 0000000..1261992
--- /dev/null
+++ b/core/modules/outside_in/css/outside_in.module.css
@@ -0,0 +1,232 @@
+/**
+ * @file
+ * Styling for Outside-In module.
+ */
+
+/* Style the offcanvas container */
+#offcanvas {
+  box-sizing: border-box;
+  position: fixed;
+  height: 100%;
+  overflow-y: auto;
+  box-shadow: -2px 3px 1px 1px rgba(0, 0, 0, 0.3333); /* LTR */
+  display: inline-block;
+  width: 25%;
+  -webkit-transform: translateX(100%);
+  -moz-transform: translateX(100%);
+  -o-transform: translateX(100%);
+  -ms-transform: translateX(100%);
+  transform: translateX(100%);
+  /* 2s delay matches transition speed to transform speed */
+  transition: ease-out all 1s 2s;
+  -webkit-transition: ease-out all 1s 2s;
+  -moz-transition: ease-out all 1s 2s;
+  z-index: 1000;
+}
+[dir="rtl"] #offcanvas {
+  text-align: right;
+  -webkit-transform: translateX(-100%);
+  -moz-transform: translateX(-100%);
+  -o-transform: translateX(-100%);
+  -ms-transform: translateX(-100%);
+  transform: translateX(-100%);
+}
+#canvas-tray-wrapper.js-tray-open #offcanvas {
+  -webkit-transform: translateX(0);
+  -moz-transform: translateX(0);
+  -o-transform: translateX(0);
+  -ms-transform: translateX(0);
+  transform: translateX(0);
+}
+#canvas-tray-wrapper #canvas-tray {
+  display: inline-block;
+  width: 100%;
+  transition: all 1s;
+  -webkit-transition: all 1s;
+  -moz-transition: all 1s;
+}
+#canvas-tray-wrapper.js-tray-open #canvas-tray {
+  left: 0; /* LTR */
+  width: 75%;
+  transition: all 1s;
+  -webkit-transition: all 1s;
+  -moz-transition: all 1s;
+}
+[dir="rtl"] #canvas-tray-wrapper.js-tray-open #canvas-tray {
+  right: 0;
+}
+/* Button that closes the offcanvas tray */
+#offcanvas > button.offcanvasClose,
+#offcanvas > button.offcanvasClose:hover {  /* @todo add hover state */
+  position: absolute;
+  right: 15px; /* LTR */
+  top: 15px;
+  height: 20px;
+  width: 20px;
+  border: 0;
+  border-radius: 0;
+  background: url(/core/misc/icons/ffffff/ex.svg) center center no-repeat;
+  color: transparent;
+  cursor: pointer;
+}
+[dir="rtl"] #offcanvas > button.offcanvasClose {
+  left: 15px;
+  right: auto;
+}
+#offcanvas h1 {
+  font-size: 120%;
+}
+[dir="rtl"] #offcanvas h1 {
+  text-align: right;
+}
+#offcanvas > .content {
+  height: 100%;
+  padding: 15px;
+}
+[dir="rtl"] #offcanvas .content{
+  text-align: right;
+}
+#canvas-tray.offCanvasDisplayInProgress {
+  position: fixed;
+  display: inline-block;
+}
+#canvas-tray.offCanvasDisplayed {
+  display: inline-block;
+}
+
+/**
+ * Buttons.
+ */
+
+/* Style for primary button */
+#offcanvas .button--primary {
+  width: auto;
+  max-width: 50%;
+  border-radius: 30px;
+  border: none;
+  background-image: -webkit-linear-gradient(top,#007bc6,#0071b8);
+  background-image: linear-gradient(to bottom,#007bc6,#0071b8);
+}
+#offcanvas .button--primary:hover,
+#offcanvas .button--primary:active {
+  background-color: inherit;
+}
+/* Style for delete button */
+#offcanvas .button--danger {
+  width: auto;
+  max-width: 50%;
+  border-radius: 0;
+  border: none;
+  background: transparent;
+}
+/* Special edit link to flow inline with form items that link to more configuration */
+#offcanvas .edit-link { /* @todo add markup and class to template */
+  display: inline-block;
+  font-size: 85%;
+  font-weight: bold;
+}
+
+/**
+ * Inputs.
+ */
+#offcanvas textarea,
+#offcanvas select,
+#offcanvas input[type="date"],
+#offcanvas input[type="datetime"],
+#offcanvas input[type="datetime-local"],
+#offcanvas input[type="email"],
+#offcanvas input[type="month"],
+#offcanvas input[type="number"],
+#offcanvas input[type="password"],
+#offcanvas input[type="search"],
+#offcanvas input[type="tel"],
+#offcanvas input[type="text"],
+#offcanvas input[type="time"],
+#offcanvas input[type="url"],
+#offcanvas input[type="week"],
+#offcanvas input[type="range"] {
+  display: block;
+  width: 100%;
+}
+/* Reduce the size of descriptions and other elements for scannability */
+#offcanvas .description,
+#offcanvas .machine-name-label,
+#offcanvas .machine-name-value {
+  font-size: 85%;
+  font-weight: normal;
+  padding-top: .25em;
+}
+
+/* Add a background to checkboxes, radios for contrast */
+/*
+#offcanvas .radio-wrapper {
+  display: inline-block;
+  padding: 1px;
+  border-radius: 20px;
+}
+#offcanvas .checkbox-wrapper {
+  display: inline-block;
+  padding: 1px;
+  border-radius: 3px;
+}
+*/
+
+/**
+ * Fieldsets.
+ */
+#offcanvas fieldset {
+  border: 0 solid transparent;
+  margin-left: -1em; /* Push background to the left edge of the parent */
+  margin-right: -1em; /* Push background to the right edge of the parent */
+  padding-top: 2.5em;
+  padding-left: 1em;
+  padding-bottom: 1em;
+  position: relative;
+}
+#offcanvas fieldset .form-item {
+  display: block;
+}
+/* Style fieldset legend similar to Drupal (seven theme) */
+#offcanvas span.fieldset-legend {
+  text-transform: uppercase;
+  letter-spacing: 0.08em;
+  font-size: 85%;
+  position: absolute;
+  top: 10px;
+}
+
+/**
+ * Admin label.
+ */
+#offcanvas .form-item-settings-admin-label {
+  font-size: 1.25em;
+  width: 100%;
+  padding: 1em;
+  padding-top: 0;
+  margin-top: 0;
+  margin-left: -1em;
+  margin-right: -1em;
+  margin-bottom: 1em;
+  border-bottom-width: 1px;
+  border-bottom-style: solid;
+}
+#offcanvas .form-item-settings-admin-label label {
+  font-size: 10px;
+  display: block;
+  margin-bottom: 0;
+}
+
+#offcanvas span.field-suffix small { /* Don't know why it's there. @todo figure out why it's rendering empty and taking space */
+  display: none;
+}
+
+/**
+ * Form actions.
+ */
+#offcanvas .form-actions {
+  padding: 1em;
+  margin-left: -1em;
+  margin-right: -1em;
+  border-bottom-width: 1px;
+  border-bottom-style: solid;
+}
diff --git a/core/modules/outside_in/css/outside_in.theme.css b/core/modules/outside_in/css/outside_in.theme.css
new file mode 100644
index 0000000..8cfd66e
--- /dev/null
+++ b/core/modules/outside_in/css/outside_in.theme.css
@@ -0,0 +1,128 @@
+/**
+ * @file
+ * Visual styling for Outside-In module.
+ *
+ * Since visual styling here differs from styling elsewhere in core,
+ * font and color styles are put here so they can be more easily changed.
+ *
+ */
+
+/* style the offcanvas container */
+#offcanvas {
+  color: #eee;
+  border-left: 1px solid #000;
+  background: #444;
+  font-family: 'Lucida Grande','Lucida Sans Unicode','Liberation Sans',sans-serif;
+}
+#offcanvas .content {
+  color: #eee;
+}
+#offcanvas h1 {
+  margin: 0 0 15px 0;
+  padding: 15px;
+  color: #eee;
+  border-bottom: 1px solid #777;
+}
+
+/**
+ * Buttons.
+ */
+#offcanvas button:hover,
+#offcanvas button:active,
+#offcanvas .button:hover,
+#offcanvas .button:active {
+  background-color: inherit;
+  background-image: inherit;
+  background-image: inherit;
+}
+/* style for primary button */
+#offcanvas .button--primary {
+  background-image: -webkit-linear-gradient(top,#007bc6,#0071b8);
+  background-image: linear-gradient(to bottom,#007bc6,#0071b8);
+  font-weight: bold;
+  text-shadow: none;
+  color: #fff;
+}
+#offcanvas .button--primary:hover,
+#offcanvas .button--primary:active {
+  background-color: inherit;
+  background-image: -webkit-linear-gradient(top,#007bc6,#0071b8); /* todo: make the hover gradient different from normal */
+  background-image: linear-gradient(to bottom,#007bc6,#0071b8);  /* todo: make the hover gradient different from normal */
+}
+/* Style for delete button */
+#offcanvas .button--danger {
+  font-weight: bold;
+  text-shadow: none;
+  color: #ff3333;
+}
+
+/**
+ * Inputs.
+ */
+#offcanvas textarea,
+#offcanvas select,
+#offcanvas input[type="date"],
+#offcanvas input[type="datetime"],
+#offcanvas input[type="datetime-local"],
+#offcanvas input[type="email"],
+#offcanvas input[type="month"],
+#offcanvas input[type="number"],
+#offcanvas input[type="password"],
+#offcanvas input[type="search"],
+#offcanvas input[type="tel"],
+#offcanvas input[type="text"],
+#offcanvas input[type="time"],
+#offcanvas input[type="url"],
+#offcanvas input[type="week"],
+#offcanvas input[type="range"] {
+  background-color: #ddd; /* darken inputs to reduce contrast on dark background */
+  border: 1px solid #333;
+}
+/* add focus effect to fields for dark background */
+#offcanvas textarea:focus,
+#offcanvas select:focus,
+#offcanvas input:focus {
+  background-color: #fff;
+}
+/* reduce the size of descriptions and other elements for scannability */
+#offcanvas .description,
+#offcanvas .machine-name-label,
+#offcanvas .machine-name-value {
+  font-family: 'Lucida Grande','Lucida Sans Unicode','Liberation Sans',sans-serif;
+  color: #bbb;
+}
+/* add a background to radio buttons for contrast */
+/*
+#offcanvas .radio-wrapper {
+  background-color: #000;
+}
+#offcanvas .checkbox-wrapper {
+  background-color: #000;
+}
+*/
+
+/**
+ * Fieldsets.
+ */
+#offcanvas fieldset {
+  background-color: #333;
+}
+
+/**
+ * Admin label.
+ */
+#offcanvas .form-item-settings-admin-label {
+  border-bottom-color: #777;
+}
+#offcanvas .form-item-settings-admin-label label {
+  color: #bbb;
+}
+
+/**
+ * Form actions.
+ */
+#offcanvas .form-actions {
+  border-bottom-color: #777;
+}
+
+
diff --git a/core/modules/outside_in/js/offcanvas.js b/core/modules/outside_in/js/offcanvas.js
new file mode 100644
index 0000000..de6f0c7
--- /dev/null
+++ b/core/modules/outside_in/js/offcanvas.js
@@ -0,0 +1,120 @@
+/**
+ * @file
+ * Drupal's off canvas library.
+ */
+
+(function ($, Drupal) {
+  'use strict';
+
+  /**
+   * Create a wrapper container for the off canvas element.
+   * @param  {number} pageWidth
+   *   The width of #page-wrapper.
+   * @return {object}
+   *   jQuery object that is the off canvas wrapper element.
+   */
+  var createOffCanvasWrapper = function (pageWidth) {
+    return $('<div />', {
+      'id': 'offcanvas',
+      'role': 'region',
+      'aria-labelledby': 'offcanvas-header'
+    });
+  };
+
+  /**
+   * Create the title element for the off canvas element.
+   * @param  {string} title
+   *   The title string.
+   * @return {object}
+   *   jQuery object that is the off canvas title element.
+   */
+  var createTitle = function (title) {
+    return $('<h1 />', {text: title, id: 'offcanvas-header'});
+  };
+
+  /**
+   * Create the actual off canvas content.
+   * @param  {string} data
+   *   This is fully rendered html from Drupal.
+   * @return {object}
+   *   jQuery object that is the off canvas content element.
+   */
+  var createOffCanvasContent = function (data) {
+    return $('<div />', {class: 'content', html: data});
+  };
+
+  /**
+   * Create the off canvas close element.
+   * @param  {object} offCanvasWrapper
+   *   The jQuery off canvas wrapper element
+   * @param  {object} pageWrapper
+   *   The jQuery off page wrapper element
+   * @return {object}
+   *   jQuery object that is the off canvas close element.
+   */
+  var createOffCanvasClose = function (offCanvasWrapper, pageWrapper) {
+    return $('<button />', {
+      'class': 'offcanvasClose',
+      'aria-label': Drupal.t('Close configuration tray.'),
+      'html': '<span class="visually-hidden">' + Drupal.t('Close') + '</span>'
+    }).click(function () {
+      pageWrapper
+        .removeClass('js-tray-open')
+        .one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function (e) {
+          Drupal.offCanvas.visible = false;
+          offCanvasWrapper.remove();
+          Drupal.announce(Drupal.t('Configuration tray closed.'));
+        }
+      );
+    });
+  };
+
+
+  /**
+   * Command to open an off canvas element.
+   *
+   * @param {Drupal.Ajax} ajax
+   *   The Drupal Ajax object.
+   * @param {object} response
+   *   Object holding the server response.
+   * @param {number} [status]
+   *   The HTTP status code.
+   */
+  Drupal.AjaxCommands.prototype.openOffCanvas = function (ajax, response, status) {
+    // Discover display/viewport size.  TODO:  work in breakpoints for tray size.
+    var $pageWrapper = $('#canvas-tray-wrapper');
+    var pageWidth = $pageWrapper.width();
+
+    // Set the initial state of the off canvas element.
+    // If the state has been set previously, use it.
+    Drupal.offCanvas = {
+      visible: (Drupal.offCanvas ? Drupal.offCanvas.visible : false)
+    };
+
+    // Construct off canvas wrapper
+    var $offcanvasWrapper = createOffCanvasWrapper(pageWidth);
+
+    // Construct off canvas internal elements.
+    var $offcanvasClose = createOffCanvasClose($offcanvasWrapper, $pageWrapper);
+    var $title = createTitle(response.dialogOptions.title);
+    var $offcanvasContent = createOffCanvasContent(response.data);
+
+    // Put everything together.
+    $offcanvasWrapper.append([$offcanvasClose, $title, $offcanvasContent]);
+
+    // Handle opening or updating tray with content.
+    if (!Drupal.offCanvas.visible) {
+      // Append content then open tray.
+      $pageWrapper.append($offcanvasWrapper);
+      Drupal.offCanvas.visible = true;
+      $pageWrapper.addClass('js-tray-open');
+      Drupal.announce(Drupal.t('Configuration tray opened.'));
+    } else {
+      // Remove previous content then append new content.
+      $pageWrapper.find('#offcanvas').remove();
+      $pageWrapper.append($offcanvasWrapper);
+      Drupal.announce(Drupal.t('Configuration tray content has been updated.'));
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/outside_in/js/outside_in.js b/core/modules/outside_in/js/outside_in.js
new file mode 100644
index 0000000..ed01e73
--- /dev/null
+++ b/core/modules/outside_in/js/outside_in.js
@@ -0,0 +1,101 @@
+/**
+ * @file
+ * Drupal's Outside In library.
+ */
+
+(function ($, Drupal) {
+  'use strict';
+
+  // Bind a listener to the 'edit' button
+  // Toggle the js-outside-edit-mode class on items that we want
+  // to disable while in edit mode.
+  $('div.contextual-toolbar-tab.toolbar-tab button').click(function (e) {
+    $('.outside-in-editable a, .outside-in-editable button')
+      .not('div.contextual a, div.contextual button')
+      .toggleClass('js-outsidein-edit-mode');
+    $('.outside-in-editable').toggleClass('focus');
+  });
+
+  // Bind an event listener to the .outside-in-editable div
+  // This listen for click events and stops default actions of those elements.
+  $('.outside-in-editable').on('click', '.js-outsidein-edit-mode', function (e) {
+    if (localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false') {
+      e.preventDefault();
+    }
+  });
+
+  // Bind an event listener to the .outside-in-editable div
+  // When a click occurs try and find the outside-in edit link
+  // and click it.
+  $('.outside-in-editable')
+    .not('div.contextual a, div.contextual button')
+    .click(function (e) {
+      if ($(e.target.offsetParent).hasClass('contextual')) {
+        return;
+      }
+      var editLink = $(e.target).find('li.outside-inblock-configure a')[0];
+
+      (editLink ? editLink : $(e.target).parents('.outside-in-editable')
+        .find('li.outside-inblock-configure a')[0])
+        .click();
+    });
+
+  /**
+   * Add Ajax behaviours to links added by contextual links
+   *
+   * @todo Fix contextual links to work with use-ajax links.
+   *   @see https://www.drupal.org/node/2764931
+   *
+   * @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.
+    data.$el.find('.use-ajax').once('ajax').each(function () {
+      // Below is copied directly from ajax.js to keep behavior the same.
+      var element_settings = {};
+      // Clicked links look better with the throbber than the progress bar.
+      element_settings.progress = {type: 'throbber'};
+
+      // For anchor tags, these will go to the target of the anchor rather
+      // than the usual location.
+      var href = $(this).attr('href');
+      if (href) {
+        element_settings.url = href;
+        element_settings.event = 'click';
+      }
+      element_settings.dialogType = $(this).data('dialog-type');
+      element_settings.dialog = $(this).data('dialog-options');
+      element_settings.base = $(this).attr('id');
+      element_settings.element = this;
+      Drupal.ajax(element_settings);
+    });
+  });
+
+  /**
+   * Attaches contextual's edit toolbar tab behavior.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches contextual toolbar behavior on a contextualToolbar-init event.
+   */
+  Drupal.behaviors.outsideinedit = {
+    attach: function (context) {
+      var editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
+      if (editMode) {
+        $('.outside-in-editable').addClass('focus');
+        var itemsToDisable = $('.outside-in-editable a, .outside-in-editable button')
+          .not('div.contextual a, div.contextual button');
+
+        itemsToDisable
+          .addClass('js-outsidein-edit-mode');
+      }
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/outside_in/outside_in.info.yml b/core/modules/outside_in/outside_in.info.yml
new file mode 100644
index 0000000..8fcb964
--- /dev/null
+++ b/core/modules/outside_in/outside_in.info.yml
@@ -0,0 +1,10 @@
+name: 'Outside In'
+type: module
+description: 'Provides the ability to access useful configuration from the Drupal front-end.'
+package: Core (Experimental)
+version: VERSION
+core: 8.x
+dependencies:
+  - block
+  - toolbar
+  - contextual
diff --git a/core/modules/outside_in/outside_in.libraries.yml b/core/modules/outside_in/outside_in.libraries.yml
new file mode 100644
index 0000000..337ebbc
--- /dev/null
+++ b/core/modules/outside_in/outside_in.libraries.yml
@@ -0,0 +1,23 @@
+drupal.outside_in:
+  version: VERSION
+  js:
+    js/outside_in.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+drupal.off_canvas:
+  version: VERSION
+  js:
+    js/offcanvas.js: {}
+  css:
+    component:
+      css/outside_in.base.css: {}
+      css/outside_in.module.css: {}
+      css/outside_in.theme.css: {}
+  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.links.contextual.yml b/core/modules/outside_in/outside_in.links.contextual.yml
new file mode 100644
index 0000000..ee83136
--- /dev/null
+++ b/core/modules/outside_in/outside_in.links.contextual.yml
@@ -0,0 +1,4 @@
+outside_in.block_configure:
+  title: 'Quick Edit'
+  route_name: 'entity.block.edit_form'
+  group: 'block'
diff --git a/core/modules/outside_in/outside_in.module b/core/modules/outside_in/outside_in.module
new file mode 100644
index 0000000..9667a57
--- /dev/null
+++ b/core/modules/outside_in/outside_in.module
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Allows configuring blocks and other configuration from the front-end of the site.
+ */
+
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\outside_in\Block\BlockEntityOffCanvasForm;
+
+/**
+ * Implements hook_help().
+ */
+function outside_in_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'help.page.outside_in':
+      $output = '<h3>' . t('About') . '</h3>';
+      // @todo Update help text.
+      $output .= '<p>' . t('The Outside In module is something that we should have help for. For more information, see the <a href=":outside-in-documentation">online documentation for the Outside In module</a>.', [':outside-in-documentation' => 'https://www.drupal.org/documentation/modules/outside_in']) . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_contextual_links_view_alter().
+ *
+ * Change Configure Blocks into offcanvas links.
+ */
+function outside_in_contextual_links_view_alter(&$element, $items) {
+  if (isset($element['#links']['outside-inblock-configure'])) {
+    $element['#links']['outside-inblock-configure']['attributes'] = [
+      'class' => ['use-ajax'],
+      'data-dialog-type' => 'offcanvas',
+    ];
+
+    $element['#attached'] = [
+      'library' => [
+        'outside_in/drupal.off_canvas',
+      ],
+    ];
+  }
+}
+
+/**
+ * Implements hook_page_top().
+ *
+ * Opens a div for consistent wrapping to all {{ page }} render in all themes.
+ */
+function outside_in_page_top(array &$page_top) {
+  $page_top['outside_in_tray_open'] = [
+    '#markup' => '<div id="canvas-tray-wrapper"><div id="canvas-tray">',
+    '#weight' => 1000
+    ];
+}
+
+/**
+ * Implements hook_page_bottom().
+ *
+ * Closes a div for consistent wrapping to all {{ page }} render in all themes.
+ */
+function outside_in_page_bottom(array &$page_bottom) {
+  $page_bottom['outside_in_tray_close'] = [
+    '#markup' => '</div></div>',
+    '#weight' => -1000
+  ];
+}
+
+/**
+ * Implements hook_block_alter().
+ */
+function outside_in_block_alter(&$definitions) {
+  foreach ($definitions as &$definition) {
+    // If no default form is defined and this plugin implements
+    // \Drupal\Core\Plugin\PluginFormInterface, use that for the default form.
+    if (!isset($definition['form']['default']) && is_subclass_of($definition['class'], PluginFormInterface::class)) {
+      $definition['form']['default'] = $definition['class'];
+    }
+  }
+}
+
+/**
+ * Implements hook_entity_type_build().
+ */
+function outside_in_entity_type_build(array &$entity_types) {
+  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+  $entity_types['block']
+    ->setFormClass('offcanvas', BlockEntityOffCanvasForm::class)
+    ->setLinkTemplate('offcanvas-form', '/admin/structure/block/manage/{block}/offcanvas');
+}
+
+/**
+ * Implements hook_preprocess_HOOK() for block templates.
+ *
+ * Adds 'outside-in-editable' class to all blocks to allow Javascript to target.
+ */
+function outside_in_preprocess_block(&$variables) {
+  // Remove on Admin routes.
+  $admin_route = \Drupal::service('router.admin_context')->isAdminRoute();
+  // @todo Check if there is actually different admin theme.
+  // Remove on Block Demo page.
+  $admin_demo = \Drupal::routeMatch()->getRouteName() === 'block.admin_demo';
+  $access = (\Drupal::currentUser()->hasPermission('administer blocks') && !$admin_route && !$admin_demo);
+
+  if (!$access) {
+    return;
+  }
+  $variables['attributes']['class'][] = 'outside-in-editable';
+}
+
+/**
+ * Implements hook_toolbar_alter().
+ *
+ * Includes outside_library if Edit link is in toolbar.
+ */
+function outside_in_toolbar_alter(&$items) {
+  if (isset($items['contextual']['tab'])) {
+    $items['contextual']['#attached']['library'][] = 'outside_in/drupal.outside_in';
+  }
+}
diff --git a/core/modules/outside_in/outside_in.routing.yml b/core/modules/outside_in/outside_in.routing.yml
new file mode 100644
index 0000000..5c81541
--- /dev/null
+++ b/core/modules/outside_in/outside_in.routing.yml
@@ -0,0 +1,7 @@
+entity.block.offcanvas_form:
+  path: '/admin/structure/block/manage/{block}/offcanvas'
+  defaults:
+    _entity_form: 'block.offcanvas'
+    _title: 'Configure block'
+  requirements:
+    _permission: 'administer blocks'
diff --git a/core/modules/outside_in/outside_in.services.yml b/core/modules/outside_in/outside_in.services.yml
new file mode 100644
index 0000000..9a785e8
--- /dev/null
+++ b/core/modules/outside_in/outside_in.services.yml
@@ -0,0 +1,9 @@
+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_offcanvas }
+  outside_in.block.manager:
+    class: Drupal\outside_in\Block\OutsideInBlockManager
+    arguments: ['@class_resolver']
diff --git a/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php b/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php
new file mode 100644
index 0000000..ef6f272
--- /dev/null
+++ b/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\outside_in\Ajax;
+
+use Drupal\Core\Ajax\OpenDialogCommand;
+
+/**
+ * Defines an AJAX command to open content in a dialog in a off canvas tray.
+ *
+ * @ingroup ajax
+ */
+class OpenOffCanvasDialogCommand extends OpenDialogCommand {
+
+  /**
+   * Constructs an OpenOffCanvasDialogCommand object.
+   *
+   * Drupal provides a built-in offcanvas tray for
+   * this purpose, so no selector needs to be provided.
+   *
+   * @todo Do we need a selector? Or act the same as modal?
+   *
+   * @param string $title
+   *   The title of the dialog.
+   * @param string|array $content
+   *   The content that will be placed in the dialog, either a render array
+   *   or an HTML string.
+   * @param array $dialog_options
+   *   (optional) Settings to be passed to the dialog implementation. Any
+   *   jQuery UI option can be used. See http://api.jqueryui.com/dialog.
+   * @param array|null $settings
+   *   (optional) Custom settings that will be passed to the Drupal behaviors
+   *   on the content of the dialog. If left empty, the settings will be
+   *   populated automatically from the current request.
+   */
+  public function __construct($title, $content, array $dialog_options = [], $settings = NULL) {
+    $dialog_options['modal'] = FALSE;
+    parent::__construct('#drupal-offcanvas', $title, $content, $dialog_options, $settings);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    $this->dialogOptions['modal'] = FALSE;
+    return [
+      'command' => 'openOffCanvas',
+      'selector' => $this->selector,
+      'settings' => $this->settings,
+      'data' => $this->getRenderedContent(),
+      'dialogOptions' => $this->dialogOptions,
+    ];
+  }
+
+}
diff --git a/core/modules/outside_in/src/Block/BlockEntityOffcanvasForm.php b/core/modules/outside_in/src/Block/BlockEntityOffcanvasForm.php
new file mode 100644
index 0000000..210b9e5
--- /dev/null
+++ b/core/modules/outside_in/src/Block/BlockEntityOffcanvasForm.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\outside_in\Block;
+
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @todo.
+ */
+class BlockEntityOffCanvasForm extends EntityForm {
+
+  /**
+   * The block entity.
+   *
+   * @var \Drupal\block\BlockInterface
+   */
+  protected $entity;
+
+  /**
+   * The block manager.
+   *
+   * @var \Drupal\outside_in\Block\OutsideInBlockManagerInterface
+   */
+  protected $blockManager;
+
+  /**
+   * The context repository service.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+   */
+  protected $contextRepository;
+
+  /**
+   * BlockEntityOffCanvasForm constructor.
+   *
+   * @param \Drupal\outside_in\Block\OutsideInBlockManagerInterface $block_manager
+   *   The block manager.
+   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
+   *   The lazy context repository service.
+   */
+  public function __construct(OutsideInBlockManagerInterface $block_manager, ContextRepositoryInterface $context_repository) {
+    $this->contextRepository = $context_repository;
+    $this->blockManager = $block_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('outside_in.block.manager'),
+      $container->get('context.repository')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    // Store theme settings in $form_state for use below.
+    $form_state->set('block_theme', $this->entity->getTheme());
+
+    // Store the gathered contexts in the form state for other objects to use
+    // during form building.
+    $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts());
+
+    $form['#tree'] = TRUE;
+    $form['#attached']['library'][] = 'block/drupal.block.admin';
+
+    $form['settings'] = $this->blockManager->getFormObject($this->entity->getPlugin(), 'sidebar')->buildConfigurationForm([], $form_state);
+
+    return $form;
+  }
+
+}
diff --git a/core/modules/outside_in/src/Block/OutsideInBlockManager.php b/core/modules/outside_in/src/Block/OutsideInBlockManager.php
new file mode 100644
index 0000000..f335d79
--- /dev/null
+++ b/core/modules/outside_in/src/Block/OutsideInBlockManager.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\outside_in\Block;
+
+use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\outside_in\OperationAwareFormInterface;
+
+/**
+ * Provides form discovery capabilities for block plugins.
+ */
+class OutsideInBlockManager implements OutsideInBlockManagerInterface {
+
+  /**
+   * The class resolver.
+   *
+   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
+   */
+  protected $classResolver;
+
+  /**
+   * OutsideInBlockManager constructor.
+   *
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   */
+  public function __construct(ClassResolverInterface $class_resolver) {
+    $this->classResolver = $class_resolver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormObject(PluginInspectionInterface $plugin, $operation) {
+    $definition = $plugin->getPluginDefinition();
+    if (!isset($definition['form'][$operation])) {
+      throw new InvalidPluginDefinitionException($plugin->getPluginId(), sprintf('The "%s" plugin did not specify a "%s" form class', $plugin->getPluginId(), $operation));
+    }
+
+    // If the form specified is the plugin itself, use it directly.
+    if (get_class($plugin) === $definition['form'][$operation]) {
+      $form_object = $plugin;
+    }
+    else {
+      $form_object = $this->classResolver->getInstanceFromDefinition($definition['form'][$operation]);
+    }
+
+    // Ensure the resulting object is a plugin form.
+    if (!$form_object instanceof PluginFormInterface) {
+      throw new InvalidPluginDefinitionException($plugin->getPluginId(), sprintf('The "%s" plugin did not specify a valid "%s" form class, must implement \Drupal\Core\Plugin\PluginFormInterface', $plugin->getPluginId(), $operation));
+    }
+
+    if ($form_object instanceof OperationAwareFormInterface) {
+      $form_object->setOperation($operation);
+    }
+
+    return $form_object;
+  }
+
+}
diff --git a/core/modules/outside_in/src/Block/OutsideInBlockManagerInterface.php b/core/modules/outside_in/src/Block/OutsideInBlockManagerInterface.php
new file mode 100644
index 0000000..4fbed53
--- /dev/null
+++ b/core/modules/outside_in/src/Block/OutsideInBlockManagerInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\outside_in\Block;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+
+/**
+ * Provides form discovery capabilities for block plugins.
+ */
+interface OutsideInBlockManagerInterface {
+
+  /**
+   * Creates a new form instance.
+   *
+   * @param \Drupal\Component\Plugin\PluginInspectionInterface $plugin
+   *   The plugin the form is for.
+   * @param string $operation
+   *   The name of the operation to use, e.g., 'default'.
+   *
+   * @return \Drupal\Core\Plugin\PluginFormInterface
+   *   A plugin form instance.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  public function getFormObject(PluginInspectionInterface $plugin, $operation);
+
+}
diff --git a/core/modules/outside_in/src/OperationAwareFormInterface.php b/core/modules/outside_in/src/OperationAwareFormInterface.php
new file mode 100644
index 0000000..cf31c58
--- /dev/null
+++ b/core/modules/outside_in/src/OperationAwareFormInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\outside_in;
+
+/**
+ * Interface for forms that are aware of what operation they are performing.
+ */
+interface OperationAwareFormInterface {
+
+  /**
+   * Sets the operation for this form.
+   *
+   * @param string $operation
+   *   The name of the current operation.
+   *
+   * @return $this
+   */
+  public function setOperation($operation);
+
+}
diff --git a/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php b/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php
new file mode 100644
index 0000000..5202f40
--- /dev/null
+++ b/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\outside_in\Render\MainContent;
+
+use Drupal\Core\Ajax\AjaxResponse;
+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;
+
+/**
+ * Default main content renderer for offcanvas dialog requests.
+ */
+class OffCanvasRender extends DialogRenderer {
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a new DialogRenderer.
+   *
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   */
+  public function __construct(TitleResolverInterface $title_resolver, RendererInterface $renderer) {
+    parent::__construct($title_resolver);
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@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 = $this->renderer->renderRoot($main_content);
+
+    // Attach the library necessary for using the OpenModalDialogCommand and set
+    // the attachments for this Ajax response.
+    $main_content['#attached']['library'][] = 'outside_in/drupal.off_canvas';
+    $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', []);
+
+    $response->addCommand(new OpenOffCanvasDialogCommand($title, $content, $options));
+    return $response;
+  }
+
+}
