 core/lib/Drupal/Core/Form/FormBuilder.php | 14 ++++++++++++--
 core/lib/Drupal/Core/Render/Element.php   |  3 ++-
 core/lib/Drupal/Core/Render/Renderer.php  | 19 +++++++++++++++++--
 3 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 40c598f..4a0d549 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -12,6 +12,7 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Access\AccessResultInterface;
 use Drupal\Core\Access\CsrfTokenGenerator;
 use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
@@ -809,6 +810,15 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
 
     // Recurse through all child elements.
     $count = 0;
+    if (isset($element['#access'])) {
+      $access = $element['#access'];
+      if ($access instanceof AccessResultInterface || $access === FALSE) {
+        $child_denied = $access;
+      }
+      else {
+        $child_access = NULL;
+      }
+    }
     foreach (Element::children($element) as $key) {
       // Prior to checking properties of child elements, their default
       // properties need to be loaded.
@@ -823,8 +833,8 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       }
 
       // Deny access to child elements if parent is denied.
-      if (isset($element['#access']) && !$element['#access']) {
-        $element[$key]['#access'] = FALSE;
+      if (isset($child_access)) {
+        $element[$key]['#access'] = $child_access;
       }
 
       // Make child elements inherit their parent's #disabled and #allow_focus
diff --git a/core/lib/Drupal/Core/Render/Element.php b/core/lib/Drupal/Core/Render/Element.php
index 67186f7..2278e7d 100644
--- a/core/lib/Drupal/Core/Render/Element.php
+++ b/core/lib/Drupal/Core/Render/Element.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Render;
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Access\AccessResultInterface;
 
 /**
  * Provides helper methods for Drupal render elements.
@@ -137,7 +138,7 @@ public static function getVisibleChildren(array $elements) {
       $child = $elements[$key];
 
       // Skip un-accessible children.
-      if (isset($child['#access']) && !$child['#access']) {
+      if (isset($child['#access']) && (($child['#access'] instanceof AccessResultInterface && !$child['#access']->isAllowed()) || !$child['#access'])) {
         continue;
       }
 
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index a91b14b..c272b90 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Access\AccessResultInterface;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Controller\ControllerResolverInterface;
@@ -168,6 +169,10 @@ public function render(&$elements, $is_root_call = FALSE) {
    * See the docs for ::render().
    */
   protected function doRender(&$elements, $is_root_call = FALSE) {
+    if (empty($elements)) {
+      return '';
+    }
+
     if (!isset($elements['#access']) && isset($elements['#access_callback'])) {
       if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) {
         $elements['#access_callback'] = $this->controllerResolver->getControllerFromDefinition($elements['#access_callback']);
@@ -176,8 +181,18 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     }
 
     // Early-return nothing if user does not have access.
-    if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
-      return '';
+    if (isset($elements['#access'])) {
+      // If #access is an AccessResultInterface object, we must apply its
+      // cacheability metadata to the render array.
+      if ($elements['#access'] instanceof AccessResultInterface) {
+        $this->addCacheableDependency($elements, $elements['#access']);
+        if (!$elements['#access']->isAllowed()) {
+          return '';
+        }
+      }
+      elseif (!$elements['#access']) {
+        return '';
+      }
     }
 
     // Do not print elements twice.
