diff --git a/core/lib/Drupal/Core/Ajax/AfterCommand.php b/core/lib/Drupal/Core/Ajax/AfterCommand.php
index 8674c73..9522783 100644
--- a/core/lib/Drupal/Core/Ajax/AfterCommand.php
+++ b/core/lib/Drupal/Core/Ajax/AfterCommand.php
@@ -34,7 +34,7 @@ public function render() {
       'command' => 'insert',
       'method' => 'after',
       'selector' => $this->selector,
-      'data' => $this->html,
+      'data' => $this->getRenderedContent(),
       'settings' => $this->settings,
     );
   }
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
index fcb45af..343c813 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -71,6 +71,15 @@ public function addCommand(CommandInterface $command, $prepend = FALSE) {
     else {
       $this->commands[] = $command->render();
     }
+    if ($command instanceof CommandWithAttachedAssetsInterface) {
+      $assets = $command->getAttachedAssets();
+      $attachments = [
+        'library' => $assets->getLibraries(),
+        'drupalSettings' => $assets->getSettings(),
+      ];
+      $attachments = drupal_merge_attached($this->attachments, $attachments);
+      $this->setAttachments($attachments);
+    }
 
     return $this;
   }
diff --git a/core/lib/Drupal/Core/Ajax/AppendCommand.php b/core/lib/Drupal/Core/Ajax/AppendCommand.php
index d758720..bf56362 100644
--- a/core/lib/Drupal/Core/Ajax/AppendCommand.php
+++ b/core/lib/Drupal/Core/Ajax/AppendCommand.php
@@ -34,7 +34,7 @@ public function render() {
       'command' => 'insert',
       'method' => 'append',
       'selector' => $this->selector,
-      'data' => $this->html,
+      'data' => $this->getRenderedContent(),
       'settings' => $this->settings,
     );
   }
diff --git a/core/lib/Drupal/Core/Ajax/BeforeCommand.php b/core/lib/Drupal/Core/Ajax/BeforeCommand.php
index 7e3e2ea..8a97a88 100644
--- a/core/lib/Drupal/Core/Ajax/BeforeCommand.php
+++ b/core/lib/Drupal/Core/Ajax/BeforeCommand.php
@@ -34,7 +34,7 @@ public function render() {
       'command' => 'insert',
       'method' => 'before',
       'selector' => $this->selector,
-      'data' => $this->html,
+      'data' => $this->getRenderedContent(),
       'settings' => $this->settings,
     );
   }
diff --git a/core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsInterface.php b/core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsInterface.php
new file mode 100644
index 0000000..5ba1fa2
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\CommandWithAttachedAssetsInterface.
+ */
+
+namespace Drupal\Core\Ajax;
+
+/**
+ * Interface for Ajax commands that render content and attach assets.
+ *
+ * All Ajax commands that render HTML should implement these methods
+ * to be able to return attached assets to the calling AjaxResponse object.
+ *
+ * @ingroup ajax
+ */
+interface CommandWithAttachedAssetsInterface {
+
+  /**
+   * Gets the attached assets.
+   *
+   * @return \Drupal\Core\Asset\AttachedAssets|null
+   *   The attached assets for this command.
+   */
+  public function getAttachedAssets();
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsTrait.php b/core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsTrait.php
new file mode 100644
index 0000000..319e3a4
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsTrait.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\CommandWithAttachedAssetsTrait.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Asset\AttachedAssets;
+
+/**
+ * Trait for Ajax commands that render content and attach assets.
+ *
+ * @ingroup ajax
+ */
+trait CommandWithAttachedAssetsTrait {
+
+  /**
+   * The attached assets for this Ajax command.
+   *
+   * @var \Drupal\Core\Asset\AttachedAssets
+   */
+  protected $attachedAssets;
+
+  /**
+   * Processes the content for output.
+   *
+   * If content is a render array, it may contain attached assets to be
+   * processed.
+   *
+   * @return string
+   *   HTML rendered content.
+   */
+  protected function getRenderedContent() {
+    if (is_array($this->content)) {
+      $html = \Drupal::service('renderer')->render($this->content);
+      $this->attachedAssets = AttachedAssets::createFromRenderArray($this->content);
+      return $html;
+    }
+    else {
+      return $this->content;
+    }
+  }
+
+  /**
+   * Gets the attached assets.
+   *
+   * @return \Drupal\Core\Asset\AttachedAssets|null
+   *   The attached assets for this command.
+   */
+  public function getAttachedAssets() {
+    return $this->attachedAssets;
+  }
+}
diff --git a/core/lib/Drupal/Core/Ajax/HtmlCommand.php b/core/lib/Drupal/Core/Ajax/HtmlCommand.php
index b9c9568..82de657 100644
--- a/core/lib/Drupal/Core/Ajax/HtmlCommand.php
+++ b/core/lib/Drupal/Core/Ajax/HtmlCommand.php
@@ -34,7 +34,7 @@ public function render() {
       'command' => 'insert',
       'method' => 'html',
       'selector' => $this->selector,
-      'data' => $this->html,
+      'data' => $this->getRenderedContent(),
       'settings' => $this->settings,
     );
   }
diff --git a/core/lib/Drupal/Core/Ajax/InsertCommand.php b/core/lib/Drupal/Core/Ajax/InsertCommand.php
index d1f0133..a0b10db 100644
--- a/core/lib/Drupal/Core/Ajax/InsertCommand.php
+++ b/core/lib/Drupal/Core/Ajax/InsertCommand.php
@@ -21,7 +21,9 @@
  *
  * @ingroup ajax
  */
-class InsertCommand implements CommandInterface {
+class InsertCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
+
+  use CommandWithAttachedAssetsTrait;
 
   /**
    * A CSS selector string.
@@ -34,11 +36,13 @@ class InsertCommand implements CommandInterface {
   protected $selector;
 
   /**
-   * The HTML content that will replace the matched element(s).
+   * The content for the matched element(s).
    *
-   * @var string
+   * Either a render array or an HTML string.
+   *
+   * @var string|array
    */
-  protected $html;
+  protected $content;
 
   /**
    * A settings array to be passed to any any attached JavaScript behavior.
@@ -52,14 +56,15 @@ class InsertCommand implements CommandInterface {
    *
    * @param string $selector
    *   A CSS selector.
-   * @param string $html
-   *   String of HTML that will replace the matched element(s).
+   * @param string|array $content
+   *   The content that will be inserted in the matched element(s), either a
+   *   render array or an HTML string.
    * @param array $settings
    *   An array of JavaScript settings to be passed to any attached behaviors.
    */
-  public function __construct($selector, $html, array $settings = NULL) {
+  public function __construct($selector, $content, array $settings = NULL) {
     $this->selector = $selector;
-    $this->html = $html;
+    $this->content = $content;
     $this->settings = $settings;
   }
 
@@ -72,7 +77,7 @@ public function render() {
       'command' => 'insert',
       'method' => NULL,
       'selector' => $this->selector,
-      'data' => $this->html,
+      'data' => $this->getRenderedContent(),
       'settings' => $this->settings,
     );
   }
diff --git a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
index 9ee09cc..d373cf8 100644
--- a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
+++ b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
@@ -14,7 +14,9 @@
  *
  * @ingroup ajax
  */
-class OpenDialogCommand implements CommandInterface {
+class OpenDialogCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
+
+  use CommandWithAttachedAssetsTrait;
 
   /**
    * The selector of the dialog.
@@ -31,11 +33,13 @@ class OpenDialogCommand implements CommandInterface {
   protected $title;
 
   /**
-   * HTML content that will placed in the dialog.
+   * The content for the dialog.
    *
-   * @var string
+   * Either a render array or an HTML string.
+   *
+   * @var string|array
    */
-  protected $html;
+  protected $content;
 
   /**
    * Stores dialog-specific options passed directly to jQuery UI dialogs. Any
@@ -60,8 +64,9 @@ class OpenDialogCommand implements CommandInterface {
    *   The selector of the dialog.
    * @param string $title
    *   The title of the dialog.
-   * @param string $html
-   *   HTML that will be placed in 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) Options to be passed to the dialog implementation. Any
    *   jQuery UI option can be used. See http://api.jqueryui.com/dialog.
@@ -70,10 +75,10 @@ class OpenDialogCommand implements CommandInterface {
    *   on the content of the dialog. If left empty, the settings will be
    *   populated automatically from the current request.
    */
-  public function __construct($selector, $title, $html, array $dialog_options = array(), $settings = NULL) {
+  public function __construct($selector, $title, $content, array $dialog_options = array(), $settings = NULL) {
     $dialog_options += array('title' => $title);
     $this->selector = $selector;
-    $this->html = $html;
+    $this->content = $content;
     $this->dialogOptions = $dialog_options;
     $this->settings = $settings;
   }
@@ -131,7 +136,7 @@ public function render() {
       'command' => 'openDialog',
       'selector' => $this->selector,
       'settings' => $this->settings,
-      'data' => $this->html,
+      'data' => $this->getRenderedContent(),
       'dialogOptions' => $this->dialogOptions,
     );
   }
diff --git a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
index ee785c3..e9b2c4f 100644
--- a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
+++ b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
@@ -25,8 +25,9 @@ class OpenModalDialogCommand extends OpenDialogCommand {
    *
    * @param string $title
    *   The title of the dialog.
-   * @param string $html
-   *   HTML that will be placed in 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.
@@ -35,8 +36,8 @@ class OpenModalDialogCommand extends OpenDialogCommand {
    *   on the content of the dialog. If left empty, the settings will be
    *   populated automatically from the current request.
    */
-  public function __construct($title, $html, array $dialog_options = array(), $settings = NULL) {
+  public function __construct($title, $content, array $dialog_options = array(), $settings = NULL) {
     $dialog_options['modal'] = TRUE;
-    parent::__construct('#drupal-modal', $title, $html, $dialog_options, $settings);
+    parent::__construct('#drupal-modal', $title, $content, $dialog_options, $settings);
   }
 }
diff --git a/core/lib/Drupal/Core/Ajax/PrependCommand.php b/core/lib/Drupal/Core/Ajax/PrependCommand.php
index d977197..6409a6a 100644
--- a/core/lib/Drupal/Core/Ajax/PrependCommand.php
+++ b/core/lib/Drupal/Core/Ajax/PrependCommand.php
@@ -34,7 +34,7 @@ public function render() {
       'command' => 'insert',
       'method' => 'prepend',
       'selector' => $this->selector,
-      'data' => $this->html,
+      'data' => $this->getRenderedContent(),
       'settings' => $this->settings,
     );
   }
diff --git a/core/lib/Drupal/Core/Ajax/ReplaceCommand.php b/core/lib/Drupal/Core/Ajax/ReplaceCommand.php
index fb2b9e5..91514a3 100644
--- a/core/lib/Drupal/Core/Ajax/ReplaceCommand.php
+++ b/core/lib/Drupal/Core/Ajax/ReplaceCommand.php
@@ -35,7 +35,7 @@ public function render() {
       'command' => 'insert',
       'method' => 'replaceWith',
       'selector' => $this->selector,
-      'data' => $this->html,
+      'data' => $this->getRenderedContent(),
       'settings' => $this->settings,
     );
   }
diff --git a/core/modules/editor/src/Form/EditorImageDialog.php b/core/modules/editor/src/Form/EditorImageDialog.php
index 80d63fd..ad87493 100644
--- a/core/modules/editor/src/Form/EditorImageDialog.php
+++ b/core/modules/editor/src/Form/EditorImageDialog.php
@@ -226,11 +226,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     if ($form_state->getErrors()) {
       unset($form['#prefix'], $form['#suffix']);
-      $status_messages = array('#theme' => 'status_messages');
-      $output = drupal_render($form);
-      $response->setAttachments($form['#attached']);
-      $output = '<div>' . drupal_render($status_messages) . $output . '</div>';
-      $response->addCommand(new HtmlCommand('#editor-image-dialog-form', $output));
+      $form['status_messages'] = [
+        '#theme' => 'status_messages',
+        '#weight' => -10,
+      ];
+      $response->addCommand(new HtmlCommand('#editor-image-dialog-form', $form));
     }
     else {
       $response->addCommand(new EditorDialogSave($form_state->getValues()));
diff --git a/core/modules/editor/src/Form/EditorLinkDialog.php b/core/modules/editor/src/Form/EditorLinkDialog.php
index 39ff4fc..feb1d1f 100644
--- a/core/modules/editor/src/Form/EditorLinkDialog.php
+++ b/core/modules/editor/src/Form/EditorLinkDialog.php
@@ -85,11 +85,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     if ($form_state->getErrors()) {
       unset($form['#prefix'], $form['#suffix']);
-      $status_messages = array('#theme' => 'status_messages');
-      $output = drupal_render($form);
-      $response->setAttachments($form['#attached']);
-      $output = '<div>' . drupal_render($status_messages) . $output . '</div>';
-      $response->addCommand(new HtmlCommand('#editor-link-dialog-form', $output));
+      $form['status_messages'] = [
+        '#theme' => 'status_messages',
+        '#weight' => -10,
+      ];
+      $response->addCommand(new HtmlCommand('#editor-link-dialog-form', $form));
     }
     else {
       $response->addCommand(new EditorDialogSave($form_state->getValues()));
