diff --git a/core/lib/Drupal/Core/Layout/LayoutDefinition.php b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
index d80a8720ca..fa9b83bda0 100644
--- a/core/lib/Drupal/Core/Layout/LayoutDefinition.php
+++ b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
@@ -9,6 +9,7 @@
 use Drupal\Component\Plugin\Definition\PluginDefinition;
 use Drupal\Core\Plugin\Definition\DependentPluginDefinitionInterface;
 use Drupal\Core\Plugin\Definition\DependentPluginDefinitionTrait;
+use Drupal\Core\Render\Element\Image;
 
 /**
  * Provides an implementation of a layout definition and its metadata.
@@ -437,13 +438,12 @@ public function setIconMap($icon_map) {
   public function getIcon($width = 125, $height = 150, $stroke_width = NULL, $padding = NULL) {
     $icon = [];
     if ($icon_path = $this->getIconPath()) {
-      $icon = [
-        '#theme' => 'image',
-        '#uri' => $icon_path,
-        '#width' => $width,
-        '#height' => $height,
-        '#alt' => $this->getLabel(),
-      ];
+      $icon = Image::getBuilder()
+        ->setUri($icon_path)
+        ->setWidth($width)
+        ->setHeight($height)
+        ->setAlt($this->getLabel())
+        ->toRenderable();
     }
     elseif ($icon_map = $this->getIconMap()) {
       $icon_builder = $this->getIconBuilder()
diff --git a/core/lib/Drupal/Core/Render/Element/FormElement.php b/core/lib/Drupal/Core/Render/Element/FormElement.php
index f911d72e85..fa29d2856e 100644
--- a/core/lib/Drupal/Core/Render/Element/FormElement.php
+++ b/core/lib/Drupal/Core/Render/Element/FormElement.php
@@ -90,6 +90,38 @@
  */
 abstract class FormElement extends RenderElement implements FormElementInterface {
 
+  public function setDefaultValue($default_value) {
+    return $this->set('default_value', $default_value);
+  }
+
+  public function setDescription($description) {
+    return $this->set('description', $description);
+  }
+
+  public function setDisabled(bool $disabled = TRUE) {
+    return $this->set('disabled', $disabled);
+  }
+
+  public function setRequired(bool $required = TRUE) {
+    return $this->set('required', $required);
+  }
+
+  public function setTitle($title) {
+    return $this->set('title', $title);
+  }
+
+  public function setTitleDisplay(string $title_display) {
+    return $this->set('title_display', $title_display);
+  }
+
+  public function setFieldPrefix($field_prefix) {
+    return $this->set('field_prefix', $field_prefix);
+  }
+
+  public function setFieldSuffix($field_suffix) {
+    return $this->set('field_suffix', $field_suffix);
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index 35814589b3..8aa918f7f4 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Render\RenderableInterface;
 use Drupal\Core\Url;
 
 /**
@@ -121,7 +122,47 @@
  *
  * @ingroup theme_render
  */
-abstract class RenderElement extends PluginBase implements ElementInterface {
+abstract class RenderElement extends PluginBase implements ElementInterface, RenderableInterface {
+
+  protected $renderable = [];
+
+  public static function getBuilder() {
+    return new static([], uniqid(), []);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toRenderable() {
+    return $this->renderable + $this->getInfo();
+  }
+
+  /**
+   * @param string $name
+   * @param $value
+   *
+   * @return $this
+   */
+  protected function set(string $name, $value) {
+    $this->renderable["#$name"] = $value;
+    return $this;
+  }
+
+  public function setThemeHook(string $theme_hook) {
+    return $this->set('theme', $theme_hook);
+  }
+
+  public function setPrefix($prefix) {
+    return $this->set('prefix', $prefix);
+  }
+
+  public function setAccess($access) {
+    return $this->set('access', $access);
+  }
+
+  public function setSuffix($suffix) {
+    return $this->set('suffix', $suffix);
+  }
 
   /**
    * {@inheritdoc}
diff --git a/core/lib/Drupal/Core/Render/Element/Textarea.php b/core/lib/Drupal/Core/Render/Element/Textarea.php
index f9406622a5..5b5d713b36 100644
--- a/core/lib/Drupal/Core/Render/Element/Textarea.php
+++ b/core/lib/Drupal/Core/Render/Element/Textarea.php
@@ -7,19 +7,13 @@
 /**
  * Provides a form element for input of multiple-line text.
  *
- * Properties:
- * - #rows: Number of rows in the text box.
- * - #cols: Number of columns in the text box.
- * - #resizable: Controls whether the text area is resizable.  Allowed values
- *   are "none", "vertical", "horizontal", or "both" (defaults to "vertical").
- * - #maxlength: The maximum amount of characters to accept as input.
- *
  * Usage example:
  * @code
- * $form['text'] = array(
- *   '#type' => 'textarea',
- *   '#title' => $this->t('Text'),
- * );
+ * use Drupal\Core\Render\Element\Textarea;
+ *
+ * $form['text'] = Textarea::getBuilder()
+ *   ->setTitle($this->t('Text'))
+ *   ->toRenderable();
  * @endcode
  *
  * @see \Drupal\Core\Render\Element\Textfield
@@ -27,7 +21,52 @@
  *
  * @FormElement("textarea")
  */
-class Textarea extends FormElement {
+class Textarea extends TextElement {
+
+  use ElementAjaxTrait;
+  use ElementAttributesTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $renderable = ['#type' => 'textarea'];
+
+  /**
+   * Sets the number of columns in the text box.
+   *
+   * @param int $cols
+   *   The number of columns in the text box.
+   *
+   * @return $this
+   */
+  public function setCols(int $cols) {
+    return $this->set('cols', $cols);
+  }
+
+  /**
+   * Sets the number of rows in the text box.
+   *
+   * @param int $rows
+   *   The number of rows in the text box.
+   *
+   * @return $this
+   */
+  public function setRows(int $rows) {
+    return $this->set('rows', $rows);
+  }
+
+  /**
+   * Sets whether the text area is resizable.
+   *
+   * @param string $resizable
+   *   Whether the text area is resizable. Allowed values are "none",
+   *   "vertical", "horizontal", or "both" (defaults to "vertical").
+   *
+   * @return $this
+   */
+  public function setResizable(string $resizable) {
+    return $this->set('resizable', $resizable);
+  }
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/aggregator/src/FeedViewBuilder.php b/core/modules/aggregator/src/FeedViewBuilder.php
index 5f50463e4e..5c0abe4193 100644
--- a/core/modules/aggregator/src/FeedViewBuilder.php
+++ b/core/modules/aggregator/src/FeedViewBuilder.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Entity\EntityViewBuilder;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Render\Element\Image;
 use Drupal\Core\Theme\Registry;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -121,11 +122,10 @@ public function buildComponents(array &$build, array $entities, array $displays,
         $label = $entity->label();
         $link_href = $entity->getWebsiteUrl();
         if ($image && $label && $link_href) {
-          $link_title = [
-            '#theme' => 'image',
-            '#uri' => $image,
-            '#alt' => $label,
-          ];
+          $link_title = Image::getBuilder()
+            ->setUri($image)
+            ->setAlt($label)
+            ->toRenderable();
           $image_link = [
             '#type' => 'link',
             '#title' => $link_title,
diff --git a/core/modules/ckeditor/ckeditor.admin.inc b/core/modules/ckeditor/ckeditor.admin.inc
index b344edff9d..1f5ce1e731 100644
--- a/core/modules/ckeditor/ckeditor.admin.inc
+++ b/core/modules/ckeditor/ckeditor.admin.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Render\Element\Image;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Language\LanguageInterface;
 
@@ -75,13 +76,12 @@ function template_preprocess_ckeditor_settings_toolbar(&$variables) {
       $value = $button['image_alternative'];
     }
     elseif (isset($button['image']) || isset($button['image' . $rtl])) {
-      $value = [
-        '#theme' => 'image',
-        '#uri' => $button['image' . $rtl] ?? $button['image'],
-        '#title' => $button['label'],
-        '#prefix' => '<a href="#" role="button" title="' . $button['label'] . '" aria-label="' . $button['label'] . '"><span class="cke_button_icon">',
-        '#suffix' => '</span></a>',
-      ];
+      $value = Image::getBuilder()
+        ->setUri($button['image' . $rtl] ?? $button['image'])
+        ->setTitle($button['label'])
+        ->setPrefix('<a href="#" role="button" title="' . $button['label'] . '" aria-label="' . $button['label'] . '"><span class="cke_button_icon">')
+        ->setSuffix('</span></a>')
+        ->toRenderable();
     }
     else {
       $value = '?';
diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module
index 2410f1db51..7aa2ad6ee5 100644
--- a/core/modules/contextual/contextual.module
+++ b/core/modules/contextual/contextual.module
@@ -9,6 +9,7 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Render\Element\Image;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
@@ -84,11 +85,10 @@ function contextual_help($route_name, RouteMatchInterface $route_match) {
       $output .= '<dd>';
       $output .= t('Contextual links for an area on a page are displayed using a contextual links button. There are two ways to make the contextual links button visible:');
       $output .= '<ol>';
-      $sample_picture = [
-        '#theme' => 'image',
-        '#uri' => 'core/misc/icons/bebebe/pencil.svg',
-        '#alt' => t('contextual links button'),
-      ];
+      $sample_picture = Image::getBuilder()
+        ->setUri('core/misc/icons/bebebe/pencil.svg')
+        ->setAlt(t('contextual links button'))
+        ->toRenderable();
       $sample_picture = \Drupal::service('renderer')->render($sample_picture);
       $output .= '<li>' . t('Hovering over the area of interest will temporarily make the contextual links button visible (which looks like a pencil in most themes, and is normally displayed in the upper right corner of the area). The icon typically looks like this: @picture', ['@picture' => $sample_picture]) . '</li>';
       $output .= '<li>' . t('If you have the <a href=":toolbar">Toolbar module</a> enabled, clicking the contextual links button in the toolbar (which looks like a pencil) will make all contextual links buttons on the page visible. Clicking this button again will toggle them to invisible.', [':toolbar' => (\Drupal::moduleHandler()->moduleExists('toolbar')) ? Url::fromRoute('help.page', ['name' => 'toolbar'])->toString() : '#']) . '</li>';
diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index 178e5464b1..87b169dd7b 100644
--- a/core/modules/image/image.admin.inc
+++ b/core/modules/image/image.admin.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Element\Image;
 
 /**
  * Prepares variables for image style preview templates.
@@ -73,17 +74,14 @@ function template_preprocess_image_style_preview(&$variables) {
   }
 
   // Build the preview of the original image.
-  $variables['original']['rendered'] = [
-    '#theme' => 'image',
-    '#uri' => $original_path,
-    '#alt' => t('Sample original image'),
-    '#title' => '',
-    '#attributes' => [
-      'width' => $variables['original']['width'],
-      'height' => $variables['original']['height'],
-      'style' => 'width: ' . $variables['preview']['original']['width'] . 'px; height: ' . $variables['preview']['original']['height'] . 'px;',
-    ],
-  ];
+  $variables['original']['rendered'] = Image::getBuilder()
+    ->setUri($original_path)
+    ->setAlt(t('Sample original image'))
+    ->setTitle('')
+    ->setAttribute('width', $variables['original']['width'])
+    ->setAttribute('height', $variables['original']['height'])
+    ->setAttribute('style', 'width: ' . $variables['preview']['original']['width'] . 'px; height: ' . $variables['preview']['original']['height'] . 'px;')
+    ->toRenderable();
 
   // Build the preview of the image style derivative. Timestamps are added
   // to prevent caching of images on the client side.
diff --git a/core/modules/system/src/Plugin/Block/SystemBrandingBlock.php b/core/modules/system/src/Plugin/Block/SystemBrandingBlock.php
index 91eaf97dc2..cf4a1dba42 100644
--- a/core/modules/system/src/Plugin/Block/SystemBrandingBlock.php
+++ b/core/modules/system/src/Plugin/Block/SystemBrandingBlock.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Render\Element\Image;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -151,12 +152,11 @@ public function build() {
     $build = [];
     $site_config = $this->configFactory->get('system.site');
 
-    $build['site_logo'] = [
-      '#theme' => 'image',
-      '#uri' => theme_get_setting('logo.url'),
-      '#alt' => $this->t('Home'),
-      '#access' => $this->configuration['use_site_logo'],
-    ];
+    $build['site_logo'] = Image::getBuilder()
+      ->setUri(theme_get_setting('logo.url'))
+      ->setAlt($this->t('Home'))
+      ->setAccess($this->configuration['use_site_logo'])
+      ->toRenderable();
 
     $build['site_name'] = [
       '#markup' => $site_config->get('name'),
diff --git a/core/modules/tour/tests/tour_test/src/Plugin/tour/tip/TipPluginImage.php b/core/modules/tour/tests/tour_test/src/Plugin/tour/tip/TipPluginImage.php
index 763b76b6af..ebae15d9b9 100644
--- a/core/modules/tour/tests/tour_test/src/Plugin/tour/tip/TipPluginImage.php
+++ b/core/modules/tour/tests/tour_test/src/Plugin/tour/tip/TipPluginImage.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Utility\Token;
+use Drupal\Core\Render\Element\Image;
 use Drupal\tour\TipPluginBase;
 use Drupal\tour\TourTipPluginInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -69,11 +70,10 @@ public static function create(ContainerInterface $container, array $configuratio
    * {@inheritdoc}
    */
   public function getBody(): array {
-    $image = [
-      '#theme' => 'image',
-      '#uri' => $this->get('url'),
-      '#alt' => $this->get('alt'),
-    ];
+    $image = Image::getBuilder()
+      ->setUri($this->get('url'))
+      ->setAlt($this->get('alt'))
+      ->toRenderable();
 
     return [
       '#type' => 'html_tag',
diff --git a/core/modules/update/update.report.inc b/core/modules/update/update.report.inc
index 2d4cd8ba60..4a17005171 100644
--- a/core/modules/update/update.report.inc
+++ b/core/modules/update/update.report.inc
@@ -5,6 +5,7 @@
  * Code required only when rendering the available updates report.
  */
 
+use Drupal\Core\Render\Element\Image;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
 use Drupal\update\ProjectRelease;
@@ -326,14 +327,13 @@ function template_preprocess_update_project_status(&$variables) {
       break;
   }
 
-  $variables['status']['icon'] = [
-    '#theme' => 'image',
-    '#width' => 18,
-    '#height' => 18,
-    '#uri' => $uri,
-    '#alt' => $text,
-    '#title' => $text,
-  ];
+  $variables['status']['icon'] = Image::getBuilder()
+    ->setWidth(18)
+    ->setHeight(18)
+    ->setUri($uri)
+    ->setAlt($text)
+    ->setTitle($text)
+    ->toRenderable();
 }
 
 /**
