diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index 330d887..a1589e7 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -949,6 +949,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 $options (optional)
+ *   An associative array of additional URL options to pass to url().
+ *
+ * @return
+ *   An array suitable for use with the ajax_render() function.
+ *
+ */
+function ajax_command_redirect($path, $options = array()) {
+  // Similar logic to drupal_goto.
+  $destination = 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
@@ -1141,3 +1173,211 @@ 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 $form_state
+ *   An array which stores information about the form.
+ * @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_form_wrapper($form_id, &$form_state) {
+  // 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(array('status_messages', 'status_messages__ajax', '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' => request()->query->all())) : $form_state['url'];
+
+    $commands[] = ajax_command_set_form($display, $title, $url);
+
+    // @todo document hook
+    drupal_alter(array('ajax_form_commands', 'ajax_form_commands_' . $form_id), $commands, $form_state);
+
+    return $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_form();
+      $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 $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 'setForm' command.
+ *
+ * @param $output
+ *   The form 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_form($output, $title, $url = NULL) {
+  $command = array(
+    'command' => 'setForm',
+    'output' => $output,
+    'title' => $title,
+  );
+  if (isset($url)) {
+    $command['url'] = $url;
+  }
+  return $command;
+}
+
+/**
+ * Creates a Drupal AJAX 'dismissForm' command.
+ *
+ * @return
+ *   An array suitable for use with the ajax_render() function.
+ */
+function ajax_command_dismiss_form() {
+  $command = array(
+    'command' => 'dismissForm',
+  );
+  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(2, '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
+ * 'use-ajax' class and are that the appropriate libraries are added with
+ * ajax_modal_prepare_form().
+ *
+ * @param $js
+ *   String either nojs or ajax.
+ * @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 $js == 'nojs' returns markup.
+ *  If $js == 'ajax' return array of ajax commands.
+ *
+ * @see ajax_modal_prepare_form().
+*/
+function ajax_modal_form($js, $form_id) {
+  // Get the form build arguments.
+  $args = func_get_args();
+  // Remove the $form_id and $form_state.
+  array_splice($args, 0, 2);
+  // Set sensible defaults if they don't already exist.
+  $form_state = array(
+    'rerender' => FALSE,
+    'ajax' => ($js == 'ajax'),
+    'no_redirect' => ($js == 'ajax'),
+    'build_info' => array(
+      'args' => $args,
+    )
+  );
+  $output = ajax_form_wrapper($form_id, $form_state);
+
+  return ($js  == 'ajax') ? array('#type' => 'ajax', '#commands' => $output) : $output;
+}
+
+/**
+ * Util to add required libraries and javascript to allow ajax modals.
+ *
+ * Modules should call this function prior to including links to modal forms to
+ * ensure the required libraries and javascript are added.
+ *
+ * For example
+ *  @code
+ *   ajax_modal_prepare_form();
+ *   $form['actions']['delete'] = array(
+ *     '#type' => 'markup',
+ *     '#value' => l (t('Delete'), 'node/' . $node->nid . '/nojs/delete', array('attributes' => array('class' => 'use-ajax')))
+ *   );
+ *  @endcode
+*/
+function ajax_modal_prepare_form() {
+  $added = drupal_static(__FUNCTION__);
+  if (!$added) {
+    drupal_add_js('core/misc/ajax.js');
+    drupal_add_library('system', 'jquery.ui.dialog');
+    drupal_add_library('system', 'jquery.form');
+    drupal_add_js(array(
+      'ajaxForm' => array(
+        'popup' => 'ajax-modal-popup',
+        'body' => 'ajax-modal-body'
+      )
+    ), 'setting');
+    $added = TRUE;
+  }
+}
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 71399c4..002bf98 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -461,6 +461,101 @@ Drupal.ajax.prototype.error = function (response, uri) {
 };
 
 /**
+ * Resize the open jquery ui modal.
+ */
+Drupal.ajax.prototype.resizeModal = function(e, no_shrink) {
+  var $ = jQuery;
+  var $modal = $('.ajax-form-dialog'); //@todo
+  var $scroll = $('.scroll', $modal); //@todo
+  if ($modal.size() == 0 || $modal.css('display') == 'none') {
+    return;
+  }
+
+  var maxWidth = parseInt($(window).width() * .85); // 85% of window
+  var minWidth = parseInt($(window).width() * .6); // 60% of window
+
+  // Set the modal to the minwidth so that our width calculation of
+  // children works.
+  $modal.css('width', minWidth);
+  var width = minWidth;
+
+  // Don't let the window get more than 80% of the display high.
+  var maxHeight = parseInt($(window).height() * .8);
+  var minHeight = 200;
+  if (no_shrink) {
+    minHeight = $modal.height();
+  }
+
+  if (minHeight > maxHeight) {
+    minHeight = maxHeight;
+  }
+
+  var height = 0;
+
+  // Calculate the height of the 'scroll' region.
+  var scrollHeight = 0;
+
+  scrollHeight += parseInt($scroll.css('padding-top'));
+  scrollHeight += parseInt($scroll.css('padding-bottom'));
+
+  $scroll.children().each(function() {
+    var w = $(this).innerWidth();
+    if (w > width) {
+      width = w;
+    }
+    scrollHeight += $(this).outerHeight(true);
+  });
+
+  // Now, calculate what the difference between the scroll and the modal
+  // will be.
+
+  var difference = 0;
+  difference += parseInt($scroll.css('padding-top'));
+  difference += parseInt($scroll.css('padding-bottom'));
+  // @todo is any of this relevant?
+  /*difference += $('.views-override').outerHeight(true);
+  difference += $('.views-messages').outerHeight(true);
+  difference += $('#views-ajax-title').outerHeight(true);
+  difference += $('.views-add-form-selected').outerHeight(true);
+  difference += $('.form-buttons', $modal).outerHeight(true);*/
+
+  height = scrollHeight + difference;
+
+  if (height > maxHeight) {
+    height = maxHeight;
+    scrollHeight = maxHeight - difference;
+  }
+  else if (height < minHeight) {
+    height = minHeight;
+    scrollHeight = minHeight - difference;
+  }
+
+  if (width > maxWidth) {
+    width = maxWidth;
+  }
+
+  // Get where we should move content to
+  var top = ($(window).height() / 2) - (height / 2);
+  var left = ($(window).width() / 2) - (width / 2);
+
+  $modal.css({
+    'top': top + 'px',
+    'left': left + 'px',
+    'width': width + 'px',
+    'height': height + 'px'
+  });
+
+  // Ensure inner popup height matches.
+  $(Drupal.settings.ajaxForm.popup).css('height', height + 'px');
+
+  $scroll.css({
+    'height': scrollHeight + 'px',
+    'max-height': scrollHeight + 'px'
+  });
+
+};
+
+/**
  * Provide a series of commands that the server can request the client perform.
  */
 Drupal.ajax.prototype.commands = {
@@ -537,6 +632,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) {
@@ -629,6 +731,68 @@ Drupal.ajax.prototype.commands = {
         document.styleSheets[0].addImport(match[1]);
       }
     }
+  },
+
+  /**
+   * Command to set a form in a modal.
+   */
+  setForm: function (ajax, response, status) {
+    Drupal.settings.ajaxForm = Drupal.settings.ajaxForm || {};
+    if (!Drupal.settings.ajaxForm.popup || typeof Drupal.settings.ajaxForm.popup == 'string') {
+      // Initalise the dialog. Modules can nominate specific ids by overriding
+      // Drupal.settings.ajaxForm.popup|title|body.
+      var popup = Drupal.settings.ajaxForm.popup = $('<div>').attr('id', Drupal.settings.ajaxForm.popup || 'ajax-form-popup');
+      Drupal.settings.ajaxForm.body = $('<div>')
+        .attr('id', Drupal.settings.ajaxForm.body || 'ajax-form-body')
+        .appendTo(popup);
+      // Create a jQuery UI dialog, but leave it closed.
+      popup.dialog({
+        'autoOpen': false,
+        'dialogClass': 'ajax-form-dialog',
+        'modal': true,
+        'position': 'center',
+        'resizable': false,
+        'width': 750
+      });
+    }
+    // Get the selectors for our title, body and popup elements.
+    var ajax_body = Drupal.settings.ajaxForm.body || '<div id="ajax-form-body">';
+    var ajax_popup = Drupal.settings.ajaxForm.popup || '<div id="ajax-form-popup">';
+    // Update our elements with the command response.
+    Drupal.settings.ajaxForm.body.html(response.output);
+    Drupal.settings.ajaxForm.popup.dialog('option', 'title', response.title);
+    // Open the jQuery.ui dialog.
+    Drupal.settings.ajaxForm.popup.dialog('open');
+    Drupal.attachBehaviors(Drupal.settings.ajaxForm.popup, ajax.settings);
+    if (response.url) {
+      // Identify the button that was clicked so that .ajaxSubmit() can use it.
+      // We need to do this for both .click() and .mousedown() since JavaScript
+      // code might trigger either behavior.
+      var $submit_buttons = $('input[type=submit], button', Drupal.settings.ajaxForm.body);
+      $submit_buttons.click(function(event) {
+        this.form.clk = this;
+      });
+      $submit_buttons.mousedown(function(event) {
+        this.form.clk = this;
+      });
+      $('.dismiss', Drupal.settings.ajaxForm.body).click(function( event ) {
+        event.preventDefault();
+        Drupal.settings.ajaxForm.popup.dialog('close');
+      })
+
+      $('form', ajax_body).once('ajax-submit-processed').each(function() {
+        var element_settings = { 'url': response.url, 'event': 'submit', 'progress': { 'type': 'throbber' } };
+        var $form = $(this);
+        var id = $form.attr('id');
+        Drupal.ajax[id] = new Drupal.ajax(id, this, element_settings);
+        Drupal.ajax[id].form = $form;
+      });
+    }
+  },
+
+  dismissForm: function (ajax, response, status) {
+    Drupal.ajax.prototype.commands.setForm({}, {'title': '', 'output': ''});
+    $(Drupal.settings.ajaxForm.popup).dialog('close');
   }
 };
 
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index d425232..515d11c 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -509,9 +509,13 @@ function node_admin_nodes() {
       );
     }
     if (node_access('delete', $node)) {
+      ajax_modal_prepare_form();
       $operations['delete'] = array(
         'title' => t('delete'),
-        'href' => 'node/' . $node->nid . '/delete',
+        'href' => 'node/' . $node->nid . '/nojs/delete',
+        'attributes' => array(
+          'class' => 'use-ajax'
+        ),
         'query' => $destination,
       );
     }
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index bb00780..345fa4b 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1979,10 +1979,10 @@ function node_menu() {
     'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
     'file' => 'node.pages.inc',
   );
-  $items['node/%node/delete'] = array(
+  $items['node/%node/%/delete'] = array(
     'title' => 'Delete',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_delete_confirm', 1),
+    'page callback' => 'ajax_modal_form',
+    'page arguments' => array(2, 'node_delete_confirm', 1),
     'access callback' => 'node_access',
     'access arguments' => array('delete', 1),
     'weight' => 1,
diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc
index 16b0ee5..15e6388 100644
--- a/core/modules/node/node.pages.inc
+++ b/core/modules/node/node.pages.inc
@@ -345,11 +345,10 @@ function node_form($form, &$form_state, Node $node) {
     '#submit' => array('node_form_build_preview'),
   );
   if (!empty($node->nid) && node_access('delete', $node)) {
+    ajax_modal_prepare_form();
     $form['actions']['delete'] = array(
-      '#type' => 'submit',
-      '#value' => t('Delete'),
-      '#weight' => 15,
-      '#submit' => array('node_form_delete_submit'),
+      '#type' => 'markup',
+      '#value' => l (t('Delete'), 'node/' . $node->nid . '/nojs/delete', array('attributes' => array('class' => 'use-ajax')))
     );
   }
   // This form uses a button-level #submit handler for the form's main submit
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 14cc390..4eb0cb2 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3176,6 +3176,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(
+        'dismiss'
+      )
+    ),
     '#options' => $options,
   );
   // By default, render the form using theme_confirm_form().
