diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index ee3e4893feca035c00a95603554569bb00c3d052..0f38132a975f62edcca9bced990b90310b077eb6 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -18,6 +18,7 @@
 use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
 use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Symfony\Component\HttpFoundation\FileBag;
 use Symfony\Component\HttpFoundation\InputBag;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -277,6 +278,7 @@ public function buildForm($form_arg, FormStateInterface &$form_state) {
       }
 
       $form = $this->retrieveForm($form_id, $form_state);
+      $this->addAsteriskExplanation($form_id, $form);
       $this->prepareForm($form_id, $form, $form_state);
 
       // self::setCache() removes uncacheable $form_state keys (see properties
@@ -638,6 +640,68 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function addAsteriskExplanation(string $form_id, array &$form): void {
+
+    $form_note_identifier = $form_id . '_required_fields_note';
+
+    foreach ($form as $form_key => $form_item) {
+      if (str_starts_with($form_key, '#')) {
+        // We'll skip over the special fields.
+        continue;
+      }
+      else {
+        // Check if type is primitive.
+        if (isset($form_item['#type']) && $this->isComplexElement($form_item['#type'])) {
+          $this->addAsteriskExplanation($form_id, $form_item);
+          if (isset($form_item[$form_note_identifier])) {
+            $form[$form_note_identifier] = $form_item[$form_note_identifier];
+            unset($form_item[$form_note_identifier]);
+            return;
+          }
+        }
+        else {
+          if (isset($form_item['#required']) && (bool) $form_item['#required'] === TRUE) {
+            $form[$form_note_identifier] = [
+              '#type' => 'container',
+              '#markup' => new TranslatableMarkup('<strong>@strong-markup: </strong><label>@label-markup *.</label>', [
+                '@strong-markup' => new TranslatableMarkup('Note'),
+                '@label-markup' => new TranslatableMarkup('Required fields are marked with an asterisk'),
+              ]),
+              '#weight' => -1000,
+            ];
+
+            return;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Checks to see if a specified type is used for grouping elements.
+   *
+   * @param string $type
+   *   String representation of the type.
+   *
+   * @return bool
+   *   Is the type in the array?
+   */
+  public function isComplexElement($type) {
+    return in_array($type, [
+      'actions',
+      'container',
+      'details',
+      'dropbutton',
+      'fieldgroup',
+      'fieldset',
+      'form',
+      'operations',
+    ]);
+  }
+
   /**
    * Renders a form action URL. It's a #lazy_builder callback.
    *
diff --git a/core/lib/Drupal/Core/Form/FormBuilderInterface.php b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
index 1a1b2240367780e1fa5a60b54d8966429f687349..d7a6fb3fe13fc4708f38af646ebdc97085a0cf0a 100644
--- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php
+++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
@@ -91,6 +91,19 @@ public function getForm($form_arg, mixed ...$args);
    */
   public function buildForm($form_arg, FormStateInterface &$form_state);
 
+  /**
+   * Checks the form to see if any of the fields are required.
+   *
+   * If there is a required field
+   * it adds a text explaining what the asterisk means.
+   *
+   * @param string $form_id
+   *   The unique string identifying the desired form.
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   */
+  public function addAsteriskExplanation(string $form_id, array &$form): void;
+
   /**
    * Constructs a new $form from the information in $form_state.
    *
diff --git a/core/modules/config/tests/src/Functional/ConfigFormOverrideTest.php b/core/modules/config/tests/src/Functional/ConfigFormOverrideTest.php
index a693d307238387cfba3c74ebc7dcfaf84c36cac1..1eb45e3d1e6edfa28a9c378a0f82ecf730109f9d 100644
--- a/core/modules/config/tests/src/Functional/ConfigFormOverrideTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigFormOverrideTest.php
@@ -70,7 +70,7 @@ public function testFormsWithOverrides(): void {
     $this->assertSession()->titleEquals('Basic site settings | ' . $overridden_name);
     $this->assertSession()->elementTextContains('css', 'div[data-drupal-messages]', self::OVERRIDE_TEXT);
     // Ensure the configuration overrides message is at the top of the form.
-    $this->assertSession()->elementExists('css', 'div[data-drupal-messages] + details#edit-site-information');
+    $this->assertSession()->elementExists('css', 'div[data-drupal-messages] + div#edit-system-site-information-settings-required-fields-note');
     $this->assertSession()->elementContains('css', 'div[data-drupal-messages]', '<a href="#edit-site-name" title="\'Site name\' form element">Site name</a>');
     $this->assertSession()->fieldValueEquals("site_name", 'Drupal');
     $this->submitForm([
diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
index d7a364170f040c938fb689523ef5359d979e3780..a3502bc839b4937f009e30db497dba97e046c6e9 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
@@ -19,6 +19,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -1002,6 +1003,26 @@ public static function providerTestFormTokenCacheability() {
     ];
   }
 
+  /**
+   * @covers ::addAsteriskExplanation
+   */
+  public function testAddAsteriskExplanation(): void {
+    $form_id = 'test_form_id';
+
+    // Tests without a required field.
+    $form = $form_id();
+    $this->formBuilder->addAsteriskExplanation($form_id, $form);
+    $this->assertArrayNotHasKey('test_form_id_required_fields_note', $form);
+
+    // Tests with a required field.
+    $form = $form_id();
+    $form['test']['#required'] = TRUE;
+    $this->formBuilder->addAsteriskExplanation($form_id, $form);
+    $this->assertEquals('container', $form['test_form_id_required_fields_note']['#type']);
+    $this->assertEquals(-1000, $form['test_form_id_required_fields_note']['#weight']);
+    $this->assertInstanceOf(TranslatableMarkup::class, $form['test_form_id_required_fields_note']['#markup']);
+  }
+
 }
 
 /**