diff --git a/assets/css/form/off-canvas.form.css b/assets/css/form/off-canvas.form.css
index 01da91d7680bf155df286e0b351db65eff5adb7c..c87f1a5b66f2d9e08d5d3a3b5e0036ef4f824625 100644
--- a/assets/css/form/off-canvas.form.css
+++ b/assets/css/form/off-canvas.form.css
@@ -5,6 +5,14 @@
  * @see core/misc/dialog/off-canvas.form.css
  */
 
+.offcanvas-end {
+  left: auto !important;
+}
+
+.offcanvas-bottom {
+  top: auto !important;
+}
+
 /* Remove form-text input styles applied on description. */
 #drupal-off-canvas:not(.drupal-off-canvas-reset) .description.form-text,
 #drupal-off-canvas-wrapper .description.form-text {
diff --git a/assets/js/misc/dialog/dialog.ajax.js b/assets/js/misc/dialog/dialog.ajax.js
index 94acfb7e659ad35c402edbd7a7af517a7f72d0cb..b846aa26d8b111e148ba26b2b4bd91087140f9a9 100644
--- a/assets/js/misc/dialog/dialog.ajax.js
+++ b/assets/js/misc/dialog/dialog.ajax.js
@@ -30,8 +30,8 @@
 
       const $dialog = $context.closest('#drupal-modal');
       if ($dialog.length) {
-        const dialogSettings = $dialog.closest('.modal').data('settings');
-        if (dialogSettings && dialogSettings.drupalAutoButtons) {
+        const drupalAutoButtons = $dialog.data('drupal-auto-buttons');
+        if (drupalAutoButtons) {
           $dialog.trigger('dialogButtonsChange');
         }
       }
@@ -102,16 +102,57 @@
     dialogUrlAjax.execute();
   };
 
+  function openOffCanvasDialog(ajax, response, status) {
+    if (!response.selector) {
+      return false;
+    }
+    let $dialog = $(response.selector);
+    if (!$dialog.length) {
+      $dialog = $(
+        `<div id="${response.selector.replace(
+          /^#/,
+          '',
+        )}" class="offcanvas" tabindex="-1" role="dialog"></div>`,
+      )
+        .appendTo('body');
+    }
+
+    // Set up the wrapper, if there isn't one.
+    if (!ajax.wrapper) {
+      ajax.wrapper = $dialog.attr('id');
+    }
+
+    // Use the ajax.js insert command to populate the dialog contents.
+    response.command = 'insert';
+    response.method = 'html';
+    if (
+      response.dialogOptions.modalDialogWrapBody === undefined ||
+      response.dialogOptions.modalDialogWrapBody === true ||
+      response.dialogOptions.modalDialogWrapBody === 'true'
+    ) {
+      response.data = `<div class="offcanvas-body">${response.data}</div>`;
+    }
+    ajax.commands.insert(ajax, response, status);
+
+    // Open the dialog itself.
+    response.dialogOptions = response.dialogOptions || {};
+    const dialog = Drupal.uiSuiteOffCanvas($dialog.get(0), response.dialogOptions);
+    if (response.dialogOptions.modal) {
+      dialog.showModal();
+    } else {
+      dialog.show();
+    }
+
+    // Add the standard Drupal class for buttons for style consistency.
+    $dialog.parent().find('.ui-dialog-buttonset').addClass('form-actions');
+  };
+
   Drupal.AjaxCommands.prototype.coreOpenDialog =
     Drupal.AjaxCommands.prototype.openDialog;
 
   Drupal.AjaxCommands.prototype.openDialog = (ajax, response, status) => {
     if (ajax.dialogRenderer === 'off_canvas') {
-      return Drupal.AjaxCommands.prototype.coreOpenDialog(
-        ajax,
-        response,
-        status,
-      );
+      return openOffCanvasDialog(ajax, response, status);
     }
 
     if (!response.selector) {
@@ -167,6 +208,7 @@
       response.dialogOptions.drupalAutoButtons =
         !!response.dialogOptions.drupalAutoButtons;
     }
+
     if (
       !response.dialogOptions.buttons &&
       response.dialogOptions.drupalAutoButtons
@@ -174,7 +216,7 @@
       response.dialogOptions.buttons =
         Drupal.behaviors.dialog.prepareDialogButtons($dialog);
     }
-
+    $dialog.data('drupal-auto-buttons', response.dialogOptions.drupalAutoButtons)
     // Bind dialogButtonsChange.
     $dialog.on('dialogButtonsChange', () => {
       const buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);
@@ -199,7 +241,11 @@
   Drupal.AjaxCommands.prototype.closeDialog = (ajax, response, status) => {
     const $dialog = $(response.selector);
     if ($dialog.length) {
-      Drupal.uiSuiteDialog($dialog.get(0)).close();
+      if ($dialog.hasClass('offcanvas')) {
+        Drupal.uiSuiteOffCanvas($dialog.get(0)).close();
+      } else {
+        Drupal.uiSuiteDialog($dialog.get(0)).close();
+      }
     }
 
     $dialog.off('dialogButtonsChange');
@@ -214,9 +260,9 @@
   };
 
   // eslint-disable-next-line
-  $(window).on("dialog:aftercreate", (e, dialog, $element, settings) => {
+  $(window).on('dialog:aftercreate', (e, dialog, $element, settings) => {
     // eslint-disable-next-line
-    $element.on("click.dialog", ".dialog-cancel", (e) => {
+    $element.on('click.dialog', '.dialog-cancel', (e) => {
       dialog.close('cancel');
       e.preventDefault();
       e.stopPropagation();
diff --git a/assets/js/misc/dialog/dialog.js b/assets/js/misc/dialog/dialog.js
index d4f270b471a8c4eedae9e8a0965fc1353fdd37cb..de9365e8218474d39b0f469bdd943ff2e4618ff5 100644
--- a/assets/js/misc/dialog/dialog.js
+++ b/assets/js/misc/dialog/dialog.js
@@ -91,6 +91,10 @@
       const modalFooter = $(
         '<div class="modal-footer"><div class="ui-dialog-buttonpane"></div><div class="ui-dialog-buttonset d-flex justify-content-end flex-grow-1"></div>',
       );
+      const $footer = $('.modal-dialog .modal-content .modal-footer', $element);
+      if ($footer.length > 0) {
+        $($footer).find('.ui-dialog-buttonset').empty();
+      }
 
       // eslint-disable-next-line func-names
       $.each(buttons, function () {
@@ -118,15 +122,13 @@
         ) {
           $(button).addClass(classes.join(' '));
         }
-
-        $(modalFooter).find('.ui-dialog-buttonset').append(button);
+        if ($footer.length > 0) {
+          $($footer).find('.ui-dialog-buttonset').append(button);
+        } else {
+          $(modalFooter).find('.ui-dialog-buttonset').append(button);
+        }
       });
-      if (
-        $('.modal-dialog .modal-content .modal-footer', $element).length > 0
-      ) {
-        $('.modal-dialog .modal-content .modal-footer', $element).remove();
-      }
-      if ($(modalFooter).html().length > 0) {
+      if ($(modalFooter).html().length > 0 && $footer.length === 0) {
         $(modalFooter).appendTo($('.modal-dialog .modal-content', $element));
       }
     }
@@ -198,11 +200,13 @@
     }
 
     function closeDialog(value) {
+      domElement.dispatchEvent(new DrupalDialogEvent('beforeclose', dialog));
       if ($element.modal !== undefined) {
         $element.modal('hide');
       }
       dialog.returnValue = value;
       dialog.open = false;
+      domElement.dispatchEvent(new DrupalDialogEvent('afterclose', dialog));
     }
 
     dialog.updateButtons = (buttons) => {
diff --git a/assets/js/misc/dialog/dialog.off-canvas.js b/assets/js/misc/dialog/dialog.off-canvas.js
new file mode 100644
index 0000000000000000000000000000000000000000..2fe0751f377b936c68a396385b07a91bc3f0e0c7
--- /dev/null
+++ b/assets/js/misc/dialog/dialog.off-canvas.js
@@ -0,0 +1,199 @@
+/**
+ * @file
+ * Dialog API inspired by HTML5 dialog element.
+ *
+ * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-dialog-element
+ */
+
+(($, Drupal, drupalSettings) => {
+  /**
+   * Default dialog options.
+   *
+   * @type {object}
+   *
+   * @prop {bool} [autoOpen=true]
+   * @prop {bool} [autoResize=undefined]
+   * @prop {bool} [backdrop=undefined]
+   * @prop {object} [classes=undefined] TODO
+   * @prop {function} close
+   * @prop {string} [dialogClasses='']
+   * @prop {string} [dialogHeadingLevel=5]
+   * @prop {string} [dialogShowHeader=true]
+   * @prop {string} [dialogShowHeaderTitle=true]
+   * @prop {string} [dialogStatic=false]
+   * @prop {bool} [drupalAutoButtons=undefined]
+   * @prop {bool} [drupalOffCanvasPosition='side']
+   * @prop {bool} [resizable=undefined]
+   * @prop {string} [title=undefined]
+   * @prop {string} [width=undefined]
+   */
+  drupalSettings.offCanvas = {
+    autoOpen: true,
+    autoResize: undefined,
+    backdrop: undefined,
+    classes: undefined,
+    close: function close(event) {
+      Drupal.uiSuiteDialog(event.target).close();
+      Drupal.detachBehaviors(event.target, null, "unload");
+    },
+    dialogHeadingLevel: 5,
+    dialogShowHeader: true,
+    dialogShowHeaderTitle: true,
+    dialogStatic: false,
+    drupalAutoButtons: undefined,
+    drupalOffCanvasPosition: "side",
+    resizable: undefined,
+    title: undefined,
+    width: undefined,
+  };
+
+  /**
+   * @typedef {object} Drupal.dialog~dialogDefinition
+   *
+   * @prop {boolean} open
+   *   Is the dialog open or not.
+   * @prop {*} returnValue
+   *   Return value of the dialog.
+   * @prop {function} show
+   *   Method to display the dialog on the page.
+   * @prop {function} showModal
+   *   Method to display the dialog as a modal on the page.
+   * @prop {function} close
+   *   Method to hide the dialog from the page.
+   */
+
+  /**
+   * Polyfill HTML5 dialog element with jQueryUI.
+   *
+   * @param {HTMLElement} element
+   *   The element that holds the dialog.
+   * @param {object} options
+   *   jQuery UI options to be passed to the dialog.
+   *
+   * @return {Drupal.dialog~dialogDefinition}
+   *   The dialog instance.
+   */
+  Drupal.uiSuiteOffCanvas = (element, options) => {
+    let undef;
+
+    const $element = $(element);
+    const domElement = $element.get(0);
+
+    const dialog = {
+      open: false,
+      returnValue: undef,
+    };
+
+    options = $.extend({}, drupalSettings.offCanvas, options);
+
+    function settingIsTrue(setting) {
+      return setting !== undefined && (setting === true || setting === "true");
+    }
+
+    function openDialog(settings) {
+      settings = $.extend({}, options, settings);
+
+      const event = new DrupalDialogEvent("beforecreate", dialog, settings);
+      domElement.dispatchEvent(event);
+      dialog.open = true;
+      settings = event.settings;
+
+      // Position
+      if (settings.drupalOffCanvasPosition === "side") {
+        $element.addClass("offcanvas-end");
+      } else if (settings.drupalOffCanvasPosition === "top") {
+        $element.addClass("offcanvas-top");
+      } else if (settings.drupalOffCanvasPosition === "bottom") {
+        $element.addClass("offcanvas-bottom");
+      }
+
+      // Classes
+      if (settings.classes) {
+        if (settings.classes['ui-dialog']) {
+          $element.addClass(settings.classes['ui-dialog']);
+        }
+        if (settings.classes['ui-dialog-content']) {
+          $('.offcanvas-body', $element).addClass(settings.classes['ui-dialog-content']);
+        }
+      }
+
+      // The modal dialog header.
+      if (settingIsTrue(settings.dialogShowHeader)) {
+        let modalHeader = '<div class="offcanvas-header">';
+        const heading = settings.dialogHeadingLevel;
+
+        if (settingIsTrue(settings.dialogShowHeaderTitle)) {
+          modalHeader += `<h${heading} class="offcanvas-title" id="offcanvasLabel">${settings.title}</h${heading}>`;
+        }
+
+        modalHeader += `<button type="button" class="close btn-close" data-bs-dismiss="offcanvas" aria-label="${Drupal.t(
+          "Close",
+        )}"></button></div>`;
+
+        $(modalHeader).prependTo($element);
+      }
+
+      if (settingIsTrue(settings.dialogStatic)) {
+        $element.attr("data-bs-backdrop", "static");
+      }
+
+      if (!settingIsTrue(settings.backdrop)) {
+        $element.attr("data-bs-scroll", "true");
+      }
+
+      if ($element.offcanvas !== undefined) {
+        $element.offcanvas(settings);
+        $element.offcanvas("show");
+      }
+
+      if (settings.width) {
+        $element.css(
+          "--bs-offcanvas-width",
+          typeof settings.width === "number"
+            ? `${settings.width}px`
+            : settings.width,
+        );
+      }
+
+      if ($element.resizable !== undefined && settings.resizable) {
+        $element.resizable({
+          handles: "w",
+        });
+      }
+
+      domElement.dispatchEvent(
+        new DrupalDialogEvent("aftercreate", dialog, settings),
+      );
+    }
+
+    function closeDialog(value) {
+      if ($element.modal !== undefined) {
+        $element.offcanvas("hide");
+      }
+      dialog.returnValue = value;
+      dialog.open = false;
+    }
+
+    dialog.show = () => {
+      openDialog({ backdrop: false });
+    };
+    dialog.showModal = () => {
+      openDialog({ backdrop: true });
+    };
+    dialog.close = () => {
+      closeDialog({});
+    };
+
+    $element.on("hide.bs.offcanvas", () => {
+      domElement.dispatchEvent(new DrupalDialogEvent("beforeclose", dialog));
+    });
+
+    $element.on("hidden.bs.offcanvas", () => {
+      domElement.dispatchEvent(new DrupalDialogEvent("afterclose", dialog));
+    });
+
+    return dialog;
+  };
+
+  Drupal.behaviors.offCanvasEvents = {};
+})(jQuery, Drupal, drupalSettings);
diff --git a/ui_suite_bootstrap.libraries.yml b/ui_suite_bootstrap.libraries.yml
index 4579c5cfb5196fcc30f41de3ca84e6084bcc6ce5..138d29b2cafe8bb777c92de5bd6d4b47fbcc853e 100644
--- a/ui_suite_bootstrap.libraries.yml
+++ b/ui_suite_bootstrap.libraries.yml
@@ -52,6 +52,12 @@ drupal.dialog.ajax:
     - core/drupal.ajax
 
 drupal.dialog.off_canvas:
+  js:
+    assets/js/misc/dialog/dialog.off-canvas.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/drupalSettings
   css:
     component:
       assets/css/form/off-canvas.button.css: {}