diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index d8eb913..b1c626e 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -5,6 +5,9 @@
  * Functions for use with Drupal's Ajax framework.
  */
 
+use Drupal\Core\Ajax\OpenDialogCommand;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+
 /**
  * @defgroup ajax Ajax framework
  * @{
@@ -1158,3 +1161,88 @@ function ajax_command_add_css($styles) {
     'data' => $styles,
   );
 }
+
+/**
+ * Build and wrap a form as OpenDialogCommand.
+ *
+ * Merges larowlan's ajax_render_modal_form and ctools-7.x's
+ * ctools_modal_form_wrapper
+ *
+ * @todo: cleanup function signature; merging the dialog_selector,
+ * dialog_settings.
+ *
+ * @return  Drupal\Core\Ajax\OpenDialogCommand
+ *
+ */
+function ajax_dialog_form_wrapper($form_id, &$form_state, $dialog_settings=array(), $dialog_selector=FALSE) {
+  // This won't override settings already in.
+  $form_state += array(
+    'rerender' => FALSE,
+    'ajax' => TRUE,
+    'no_redirect' => TRUE,
+  );
+
+  $output = drupal_build_form($form_id, $form_state);
+  return ajax_dialog_form_render($form_state, $output, $dialog_settings, $dialog_selector);
+}
+
+/**
+ * Render a form output as a dialog command.
+ *
+ * @return  Drupal\Core\Ajax\OpenDialogCommand
+ */
+function ajax_dialog_form_render($form_state, $output, $dialog_settings=array(), $dialog_selector=FALSE) {
+  $output = drupal_render($output);
+
+  $title = empty($form_state['title']) ? drupal_get_title() : $form_state['title'];
+  $url = empty($form_state['url']) ?
+    url(
+      current_path(),
+      array(
+        'absolute' => TRUE,
+        'query' => drupal_container()->get('request')->query->all()
+      )
+    ) : $form_state['url'];
+
+  // If there are messages for the form, render them.
+  // @todo: perhaps put this into the AjaxRender class, as it seems to be
+  // something fundamental like css/js (@dawehner).
+  if ($messages = theme('status_messages')) {
+    $output = $messages . $output;
+  }
+
+  // Modal dialog if no selector or the selector is #drupal-modal.
+  $modal = !$dialog_selector || $dialog_selector == '#drupal-modal';
+
+  if ($modal) {
+    return new OpenModalDialogCommand(
+      $title,
+      $output,
+      $url,
+      $dialog_settings
+    );
+  } else {
+    return new OpenDialogCommand(
+      $dialog_selector,
+      $title,
+      $output,
+      $url,
+      $dialog_settings
+    );
+  }
+
+}
+
+/**
+ * Helper function that checks whether this is an AJAX request.
+ *
+ * @return bool
+ *   Return TRUE if the current request is asking for ajax content
+ */
+function ajax_is_ajax_request() {
+  $container = drupal_container();
+  if ($container->has('content_negotiation') && $container->isScopeActive('request')) {
+    return $container->get('content_negotiation')->getContentType($container->get('request')) == 'ajax';
+  }
+  return FALSE;
+}
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 4a1e327..69fce4e 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -566,8 +566,45 @@ Drupal.ajax.prototype.commands = {
     // @todo Consider whether this is overloading title inappropriately, and
     //   if so, find another way to determine dialog eligibility.
     if (ajax.dialog && ('title' in response)) {
-      var dialog = Drupal.dialog(wrapper, {title: response.title});
+      // Assemble dialog options: starting with title extend it by the settings
+      // "on" the element and then overriding it with response settings.
+      // @todo: clean this up?
+      var dialogOptions = $.extend({title: response.title},
+        ajax.dialog.settings,
+        response.dialog ? response.dialog.settings : {}
+      );
+      var dialog = Drupal.dialog(wrapper, dialogOptions);
       ajax.dialog.modal ? dialog.showModal() : dialog.show();
+      // Wire-up any inserted forms in the dialog.
+      // @see core/modules/views/js/ajax.js:Drupal.ajax.prototype.commands.viewsSetForm
+      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 = wrapper.find('input[type=submit], button');
+        $submit_buttons.click(function(event) {
+          this.form.clk = this;
+        });
+        $submit_buttons.mousedown(function(event) {
+          this.form.clk = this;
+        });
+
+        wrapper.find('form').once('ajax-modal-processed').each(function() {
+          // @todo: should we allow any this to be overridden by request-specific stuff?
+          var element_settings = {
+            'url': response.url,
+            'event': 'submit',
+            'progress': { 'type': 'throbber' },
+            // Keep the form in the dialog
+            // @todo: override w/ response.dialog?
+            'dialog': ajax.dialog
+          };
+          var $form = $(this);
+          var id = $form.attr('id');
+          Drupal.ajax[id] = new Drupal.ajax(id, this, element_settings);
+          Drupal.ajax[id].form = $form;
+        });
+      }
     }
 
     // Immediately hide the new content if we're using any effects.
diff --git a/core/misc/dialog.js b/core/misc/dialog.js
index c0bccf1..70e7a5e 100644
--- a/core/misc/dialog.js
+++ b/core/misc/dialog.js
@@ -48,7 +48,8 @@ Drupal.dialog = function (element, options) {
 
   var undef;
   var $element = $(element);
-  var defaults = $.extend(options, drupalSettings.dialog);
+  // Extend the default settings in drupalSettings with the local options.
+  var defaults = $.extend({}, drupalSettings.dialog, options);
   var dialog = {
     open: false,
     returnValue: undef,
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php
index 2e6c421..8845e66 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php
@@ -24,6 +24,9 @@
 use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\Ajax\RestripeCommand;
 use Drupal\Core\Ajax\SettingsCommand;
+use Drupal\Core\Ajax\OpenDialogCommand;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Ajax\CloseDialogCommand;
 
 /**
  * Tests for all AJAX Commands.
@@ -305,5 +308,71 @@ function testSettingsCommand() {
     $this->assertEqual($command->render(), $expected, 'SettingsCommand::render() returns a proper array.');
   }
 
-}
+  /**
+   * Tests that SettingsCommand objects can be constructed and rendered.
+   */
+  function testOpenDialogCommand() {
+    $command = new OpenDialogCommand('#some-dialog', 'Title', '<p>Text!</p>');
+
+    $expected = array(
+      'command' => 'openDialog',
+      'selector' => '#some-dialog',
+      'title' => 'Title',
+      'settings' => NULL,
+      'data' => '<p>Text!</p>',
+      'url' => NULL,
+      'dialog' => array(
+        'modal' => FALSE,
+        'settings' => NULL
+      )
+    );
+
+    $this->assertEqual($command->render(), $expected, 'OpenDialogCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that SettingsCommand objects can be constructed and rendered.
+   */
+  function testOpenModalDialogCommand() {
+    $command = new OpenModalDialogCommand('Title', '<p>Text!</p>');
+
+    $expected = array(
+      'command' => 'openModalDialog',
+      'selector' => '#drupal-modal',
+      'title' => 'Title',
+      'settings' => NULL,
+      'data' => '<p>Text!</p>',
+      'url' => NULL,
+      'dialog' => array(
+        'modal' => TRUE,
+        'settings' => NULL
+      )
+    );
 
+    $this->assertEqual($command->render(), $expected, 'OpenModalDialogCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that SettingsCommand objects can be constructed and rendered.
+   */
+  function testCloseDialogCommand() {
+    $command = new CloseDialogCommand();
+    $expected = array(
+      'command' => 'closeDialog',
+      'selector' => '#drupal-modal',
+      'settings' => NULL,
+    );
+
+    $this->assertEqual($command->render(), $expected, 'CloseDialogCommand::render() returns a proper array.');
+
+    $command = new CloseDialogCommand('#some-dialog');
+    $expected = array(
+      'command' => 'closeDialog',
+      'selector' => '#some-dialog',
+      'settings' => NULL,
+    );
+
+    $this->assertEqual($command->render(), $expected, 'CloseDialogCommand::render() with a selector returns a proper array.');
+  }
+
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 336f84b..2906d83 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1299,6 +1299,8 @@ function system_library_info() {
     'version' => VERSION,
     'js' => array(
       'core/misc/dialog.js' => array('group' => JS_LIBRARY),
+      // @todo: does it make sense to have dialog w/o ajax?
+      'core/misc/dialog.ajax.js' => array('group' => JS_LIBRARY, 'weight' => 3),
     ),
     'dependencies' => array(
       array('system', 'jquery'),
@@ -3393,6 +3395,13 @@ function confirm_form($form, $question, $path, $description = NULL, $yes = NULL,
     '#type' => 'submit',
     '#value' => $yes ? $yes : t('Confirm'),
   );
+
+  if (!isset($options['attributes']) || !isset($options['attributes']['class'])) {
+    $options['attributes']['class'] = array();
+  }
+  // Add the class for improved UX. OTOH, this is not proper.
+  $options['attributes']['class'][] = 'dialog-cancel';
+
   $form['actions']['cancel'] = array(
     '#type' => 'link',
     '#title' => $no ? $no : t('Cancel'),
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module
index efb1966..536b9ef 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.module
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module
@@ -1,5 +1,9 @@
 <?php
 
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Ajax\CloseDialogCommand;
+
 /**
  * @file
  * Helper module for Ajax framework tests.
@@ -36,6 +40,16 @@ function ajax_test_menu() {
     'page callback' => 'ajax_test_dialog_contents',
     'access callback' => TRUE,
   );
+  $items['ajax-test/dialog-commands'] = array(
+    'title' => 'AJAX Dialog commands',
+    'page callback' => 'ajax_test_dialog_commands',
+    'access callback' => TRUE,
+  );
+  $items['ajax-test/dialog-commands-close'] = array(
+    'title' => 'AJAX Dialog commands: close',
+    'page callback' => 'ajax_test_dialog_commands_close',
+    'access callback' => TRUE,
+  );
   return $items;
 }
 
@@ -95,6 +109,7 @@ function ajax_test_dialog() {
 
   // Dialog behavior applied to a button.
   $build['form'] = drupal_get_form('ajax_test_dialog_form');
+  $build['form']['#attached']['library'][] = array('system', 'drupal.dialog');
 
   // Dialog behavior applied to a #type => 'link'.
   $build['link'] = array(
@@ -125,6 +140,13 @@ function ajax_test_dialog() {
           'wrapper' => 'ajax-test-dialog-wrapper-2',
         ),
       ),
+      'link4' => array(
+        'title' => 'Link 4 (modal with ajax commands)',
+        'href' => 'ajax-test/dialog-commands',
+        'ajax' => array(
+          'dialog' => array('modal' => TRUE),
+        ),
+      ),
     ),
   );
   return $build;
@@ -181,3 +203,39 @@ function ajax_test_dialog_contents() {
   );
 }
 
+/**
+ * Menu callback: Returns ajax commands to open a dialog.
+ */
+function ajax_test_dialog_commands() {
+  $response = new AjaxResponse();
+
+  $content = array(
+    'content' => array(
+      '#markup' => 'Example message',
+    ),
+    'cancel'=> array(
+      '#type' => 'link',
+      '#title' => 'Cancel',
+      '#href' => 'ajax-test/dialog-commands-close',
+      '#ajax' => array(),
+    ),
+  );
+
+  $content = drupal_render($content);
+  // @todo: This currently only tests the modal. let's
+  $response->addCommand(new OpenModalDialogCommand(t('Title'), $content));
+
+  return $response;
+}
+
+/**
+ * Menu callback: Close the ajax dialog.
+ */
+function ajax_test_dialog_commands_close() {
+  $response = new AjaxResponse();
+
+  $response->addCommand(new CloseDialogCommand());
+
+  return $response;
+}
+
