diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index 45bb706..e05ac74 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -76,7 +76,6 @@
  *       'wrapper' => 'replace_textfield_div',
  *      ),
  *   );
-
  *   // This entire form element will be replaced with an updated value.
  *   $form['replace_textfield'] = array(
  *     '#type' => 'textfield',
@@ -948,6 +947,38 @@ function ajax_command_remove($selector) {
 }
 
 /**
+ * Creates a Drupal Ajax 'redirect' command.
+ *
+ * The 'redirect' command instructs the browser to redirect to the give url.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.redirect()
+ * defined in misc/ajax.js.
+ *
+ * @param $path
+ *   An internal drupal path.
+ * @param (optional) $options
+ *   An associative array of additional URL options to pass to url(). Defaults
+ *   to array().
+ *
+ * @return
+ *   An array suitable for use with the ajax_render() function.
+ */
+function ajax_command_redirect($path, $options = array()) {
+  // Similar logic to drupal_goto.
+  $destination = drupal_container()->get('request')->query->get('destination');
+  if (!empty($destination) && !url_is_external($destination)) {
+    $parsed_destination = drupal_parse_url($destination);
+    $path = $parsed_destination['path'];
+    $options['query'] = $parsed_destination['query'];
+    $options['fragment'] = $parsed_destination['fragment'];
+  }
+  return array(
+    'command' => 'redirect',
+    'href' => url($path, $options),
+  );
+}
+
+/**
  * Creates a Drupal Ajax 'changed' command.
  *
  * This command instructs the client to mark each of the elements matched by the
@@ -1140,3 +1171,183 @@ function ajax_command_add_css($styles) {
     'data' => $styles,
   );
 }
+
+/**
+ * Wrapper around drupal_build_form to handle rendering forms in a jquery.ui
+ * dialog. Automatically handles a number of AJAX tasks.
+ *
+ * @param $form_id
+ *   The unique string identifying the desired form.
+ * @param $args
+ *   An array of arguments for the form builder.
+ * @param ...
+ *   Any additional arguments are passed on to drupal_build_form(), including
+ *   the unique form constructor function.
+ *
+ * @return
+ *   An array of ajax commands.
+ *
+ * @see drupal_get_form()
+ * @see drupal_build_form()
+ * @see ajax_deliver()
+ */
+function ajax_render_modal_form($form_id, $args) {
+  // Check for the content-type.
+  $container = drupal_container();
+  $type = FALSE;
+  if ($container->has('request') && $container->has('content_negotiation')) {
+    $type = $container->get('content_negotiation')->getContentType($container->get('request'));
+  }
+  // Set sensible defaults.
+  $form_state = array(
+    'rerender' => FALSE,
+    'ajax' => ($type == 'ajax'),
+    'no_redirect' => ($type == 'ajax'),
+    'build_info' => array(
+      'args' => $args,
+    )
+  );
+  // Build and render the form.
+  $form = drupal_build_form($form_id, $form_state);
+  $output = drupal_render($form);
+  // Some forms may have the title built in, if so set the title here (for non
+  // javascript users):
+  if (empty($form_state['ajax']) && !empty($form_state['title'])) {
+    drupal_set_title($form_state['title']);
+  }
+
+  if (!empty($form_state['ajax']) && (empty($form_state['executed']) || !empty($form_state['rerender']))) {
+    // If the form didn't execute and we're using ajax, build up a
+    // Ajax command list to execute.
+    $commands = array();
+
+    $display = '';
+    if ($messages = theme('status_messages__ajax__' . $form_id)) {
+      $display = $messages;
+    }
+    $display .= $output;
+
+    $title = empty($form_state['title']) ? drupal_get_title() : $form_state['title'];
+    // @todo document hook
+    drupal_alter(array('ajax_form_title', 'ajax_form_title_' . $form_id), $title, $form_state);
+
+    $url = empty($form_state['url']) ? url(current_path(), array('absolute' => TRUE, 'query' => drupal_container()->get('request')->query->all())) : $form_state['url'];
+
+    $commands[] = ajax_command_set_modal($display, $title, $url);
+
+    // @todo document hook
+    drupal_alter(array('ajax_form_commands', 'ajax_form_commands_' . $form_id), $commands, $form_state);
+
+    return array('#type' => 'ajax', '#commands' => $commands);
+  }
+  elseif ($form_state['submitted'] && empty($form_state['rerender'])) {
+    if (!empty($form_state['ajax'])) {
+      // Form has submitted, dismiss it.
+      $commands = array();
+      $commands[] = ajax_command_dismiss_modal();
+      $redirect = current_path();
+      if (isset($form_state['redirect'])) {
+        if (is_array($form_state['redirect'])) {
+          $redirect = end($form_state['redirect']);
+        }
+        else {
+          $redirect = $form_state['redirect'];
+        }
+      }
+      $commands[] = ajax_command_redirect($redirect);
+      drupal_alter(array('ajax_form_commands', 'ajax_form_commands_' . $form_id), $commands, $form_state);
+      return array('#type' => 'ajax', '#commands' => $commands);
+    }
+    // Degrade to non-javascript behaviour.
+    drupal_redirect_form($form_state);
+  }
+
+  // Non javascript invocation - degrade to standard drupal_build_form
+  // behaviour. Forms can have the title built in, if so set the title here:
+  if (empty($form_state['ajax']) && !empty($form_state['title'])) {
+    drupal_set_title($form_state['title']);
+  }
+
+  return $output;
+}
+
+/**
+ * Creates a Drupal AJAX 'modalOpen' command.
+ *
+ * @param $html
+ *   The html to display in the modal.
+ * @param $title
+ *   The title.
+ * @param $url
+ *   An optional URL.
+ *
+ * @return
+ *   An array suitable for use with the ajax_render() function.
+ */
+function ajax_command_set_modal($html, $title, $url = NULL) {
+  $command = array(
+    'command' => 'modalOpen',
+    'data' => $html,
+    'title' => $title,
+  );
+  if (isset($url)) {
+    $command['url'] = $url;
+  }
+  return $command;
+}
+
+/**
+ * Creates a Drupal AJAX 'modalClose' command.
+ *
+ * @return
+ *   An array suitable for use with the ajax_render() function.
+ */
+function ajax_command_dismiss_modal() {
+  $command = array(
+    'command' => 'modalClose',
+  );
+  return $command;
+}
+
+/**
+ * Form builder wrapper for displaying ajax forms in modals.
+ *
+ * Modules should use this as a menu callback to handle displaying forms in
+ * modals. Example hook_menu () item
+ *  @code
+ *   $items['node/%node/delete'] = array(
+ *    'title' => 'Delete',
+ *    'page callback' => 'ajax_modal_form',
+ *    'page arguments' => array('node_delete_confirm', 1),
+ *    'access callback' => 'node_access',
+ *    'access arguments' => array('delete', 1),
+ *    'weight' => 1,
+ *    'type' => MENU_LOCAL_TASK,
+ *    'context' => MENU_CONTEXT_INLINE,
+ *    'file' => 'node.pages.inc',
+ *   );
+ * @endcode
+ * In addition modules should ensure their links to ajax modals contain the
+ * 'modal' flag.
+ *
+ * @param $form_id
+ *   The unique string identifying the desired form.
+ * @param ...
+ *   Any additional arguments are passed on to drupal_build_form(), including
+ *   the unique form constructor function.
+ *
+ * @return mixed
+ *   If request Content-Type is ajax, returns array of ajax commands, otherwise
+ *   returns markup from drupal_get_form()
+ *
+ * @see l()
+ */
+function ajax_modal_form($form_id) {
+  // Get the form build arguments.
+  $args = func_get_args();
+  // Remove the $form_id.
+  array_splice($args, 0, 1);
+  $output = ajax_render_modal_form($form_id, $args);
+
+  return $output;
+}
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 7fa79d1..7823412 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -2306,6 +2306,9 @@ function drupal_http_header_attributes(array $attributes = array()) {
  *     internal to the site, $options['language'] is used to determine whether
  *     the link is "active", or pointing to the current page (the language as
  *     well as the path must match). This element is also used by url().
+ *   - 'modal': Pass TRUE if the link should display the form in a modal, note
+ *     that the page callback should be ajax_modal_form() in order for this to
+ *     work.
  *   - Additional $options elements used by the url() function.
  *
  * @return string
@@ -2323,6 +2326,11 @@ function l($text, $path, array $options = array()) {
     'html' => FALSE,
   );
 
+  if (!empty($options['modal'])) {
+    $options['attributes']['class'][] = 'use-ajax';
+    drupal_add_library('system', 'drupal.ajax.modal');
+  }
+
   // Append active class.
   // The link is only active, if its path corresponds to the current path, the
   // language of the linked path is equal to the current language, and if the
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 58db308..e83db7c 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -40,6 +40,12 @@ Drupal.behaviors.AJAX = {
       }
     }
 
+    // Prevent ajax links to move page.
+    $('body').once('ajax-link').on('click', '.use-ajax', function (e) {
+      e.preventDefault();
+      e.stopPropagation();
+    });
+
     // Bind Ajax behaviors to all items showing the class.
     $('.use-ajax').once('ajax', function () {
       var element_settings = {};
@@ -547,6 +553,13 @@ Drupal.ajax.prototype.commands = {
   },
 
   /**
+   * Command to redirect a user.
+   */
+  redirect: function (ajax, response, status) {
+    window.location.href = response.href;
+  },
+
+  /**
    * Command to remove a chunk from the page.
    */
   remove: function (ajax, response, status) {
diff --git a/core/misc/ajax.modal.js b/core/misc/ajax.modal.js
new file mode 100644
index 0000000..a8171d7
--- /dev/null
+++ b/core/misc/ajax.modal.js
@@ -0,0 +1,54 @@
+(function ($, Drupal, drupalSettings) {
+
+  "use strict";
+
+  var $modal;
+  var modalConfig = {
+    //autoOpen: false,
+    modal: true,
+    dialogClass: '',
+    close: function (e) {
+      Drupal.detachBehaviors(e.target, null, 'unload');
+      // remove everything from the DOM like the previous modal dialog.
+      $(e.target).dialog('destroy').remove();
+    }
+  };
+
+  $.extend(Drupal.ajax.prototype.commands, {
+    /**
+     *
+     */
+    modalOpen: function (ajax, response, status) {
+      var modalSettings = $.extend({title: response.title}, modalConfig, drupalSettings.modal);
+      // Clean up all Modal behaviors.
+      $modal = $('#drupal-modal');
+      if ($modal.length) {
+        Drupal.detachBehaviors($modal[0], null, 'unload');
+        $modal.dialog('destroy').remove();
+      }
+      $modal = $('<div id="drupal-modal">');
+      $('body').append($modal);
+
+      // trigger a global event to allow scripts to bind events to the dialog
+      $(window).trigger('modal:beforecreate', [$modal, modalSettings]);
+      $modal.dialog(modalSettings);
+      $(window).trigger('modal:aftercreate', [$modal, modalSettings]);
+
+      // Let scripts use Drupal.behaviors to do things to the content.
+      ajax.wrapper = $modal;
+      Drupal.ajax.prototype.commands.insert(ajax, {
+        method: 'html',
+        data: response.data
+      }, status);
+
+      $modal.on('click', '.modal-dismiss', function (e) {
+        $modal.dialog('close');
+      });
+    },
+
+    modalClose: function (ajax, response, status) {
+      $modal.dialog('close');
+    }
+  });
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/forum/forum.admin.inc b/core/modules/forum/forum.admin.inc
index e3c564a..dae6604 100644
--- a/core/modules/forum/forum.admin.inc
+++ b/core/modules/forum/forum.admin.inc
@@ -75,7 +75,16 @@ function forum_form_forum($form, &$form_state, $edit = array()) {
   $form['actions'] = array('#type' => 'actions');
   $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
   if ($edit['tid']) {
-    $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+    $form['actions']['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#attached' => array(
+        'library' => array(array('system', 'drupal.ajax.modal'))
+      ),
+      '#ajax' => array(
+        'callback' => 'forum_confirm_delete_ajax'
+      )
+    );
     $form['tid'] = array('#type' => 'hidden', '#value' => $edit['tid']);
   }
   $form['#submit'][] = 'forum_form_submit';
@@ -190,7 +199,13 @@ function forum_form_container($form, &$form_state, $edit = array()) {
     '#value' => t('Save')
   );
   if ($edit['tid']) {
-    $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+    $form['actions']['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#ajax' => array(
+        'callback' => 'forum_confirm_delete_ajax'
+      )
+    );
     $form['tid'] = array('#type' => 'value', '#value' => $edit['tid']);
   }
   $form['#submit'][] = 'forum_form_submit';
@@ -200,6 +215,15 @@ function forum_form_container($form, &$form_state, $edit = array()) {
 }
 
 /**
+ * Ajax callback for deleting a forum.
+ */
+function forum_confirm_delete_ajax($form, &$form_state) {
+  return ajax_render_modal_form('forum_confirm_delete', array(
+    $form_state['values']['tid']
+  ));
+}
+
+/**
  * Form constructor for confirming deletion of a forum taxonomy term.
  *
  * @param $tid
@@ -209,6 +233,7 @@ function forum_form_container($form, &$form_state, $edit = array()) {
  * @ingroup forms
  */
 function forum_confirm_delete($form, &$form_state, $tid) {
+  $form['#action'] = url('admin/structure/forum/edit/forum/' . $tid);
   $term = taxonomy_term_load($tid);
 
   $form['tid'] = array('#type' => 'value', '#value' => $tid);
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index bca4dc2..6f79b94 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -557,6 +557,7 @@ function node_admin_nodes() {
       $operations['delete'] = array(
         'title' => t('delete'),
         'href' => 'node/' . $node->nid . '/delete',
+        'modal' => TRUE,
         'query' => $destination,
       );
     }
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index a6a64ae..c1d2c43 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1793,7 +1793,7 @@ function node_menu() {
   );
   $items['node/%node/delete'] = array(
     'title' => 'Delete',
-    'page callback' => 'drupal_get_form',
+    'page callback' => 'ajax_modal_form',
     'page arguments' => array('node_delete_confirm', 1),
     'access callback' => 'node_access',
     'access arguments' => array('delete', 1),
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 843ed78..5f1123d 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1170,6 +1170,23 @@ function system_library_info() {
     ),
   );
 
+  // Drupal's Ajax framework, modal component.
+  $libraries['drupal.ajax.modal'] = array(
+    'title' => 'Drupal AJAX modals',
+    'website' => 'http://api.drupal.org/api/group/ajax/8',
+    'version' => VERSION,
+    'js' => array(
+      'core/misc/ajax.modal.js' => array('group' => JS_LIBRARY, 'weight' => 2),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+      array('system', 'drupal'),
+      array('system', 'drupalSettings'),
+      array('system', 'drupal.ajax'),
+      array('system', 'jquery.ui.dialog')
+    ),
+  );
+
   // Drupal's batch API.
   $libraries['drupal.batch'] = array(
     'title' => 'Drupal batch API',
@@ -3370,6 +3387,11 @@ function confirm_form($form, $question, $path, $description = NULL, $yes = NULL,
     '#type' => 'link',
     '#title' => $no ? $no : t('Cancel'),
     '#href' => $options['path'],
+    '#attributes' => array(
+      'class' => array(
+        'modal-dismiss'
+      )
+    ),
     '#options' => $options,
   );
   // By default, render the form using theme_confirm_form().
