diff --git a/core/lib/Drupal/Core/Form/ConfigFormBase.php b/core/lib/Drupal/Core/Form/ConfigFormBase.php
index b9af2323fc6f01185f8097ea8822f36c8dca3e1f..7a200baf86dc532d6b3a06b99e4a2584e169a093 100644
--- a/core/lib/Drupal/Core/Form/ConfigFormBase.php
+++ b/core/lib/Drupal/Core/Form/ConfigFormBase.php
@@ -137,7 +137,6 @@ public function storeConfigKeyToFormElementMap(array $element, FormStateInterfac
       if (is_string($target)) {
         $target = ConfigTarget::fromString($target);
       }
-      $target->elementName = $element['#name'];
       $target->elementParents = $element['#parents'];
       $map[$target->configName . ':' . $target->propertyPath] = $target;
       $form_state->set(static::CONFIG_KEY_TO_FORM_ELEMENT_MAP, $map);
@@ -192,7 +191,20 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
           // will not have the sequence index in it.
           $property_path = rtrim($property_path, '0123456789.');
         }
-        $form_element_name = $map["$config_name:$property_path"]->elementName;
+
+        if ($property_path === '') {
+          // There is a map to a non-existing config key. Try to work backwards.
+          $property_path = $violation->getParameters()['@key'] ?? '';
+        }
+
+        if (isset($map["$config_name:$property_path"])) {
+          $form_element_name = implode('][', $map["$config_name:$property_path"]->elementParents);
+        }
+        else {
+          // We cannot determine where to place the violation. The only option
+          // is the entire form.
+          $form_element_name = '';
+        }
         $violations_per_form_element[$form_element_name][$index] = $violation;
       }
 
diff --git a/core/lib/Drupal/Core/Form/ConfigTarget.php b/core/lib/Drupal/Core/Form/ConfigTarget.php
index 45eee5799dfa47c1724674ceac2f55a5fc3f49ef..ddf7bc50007191181ddea394c393089d31b928ab 100644
--- a/core/lib/Drupal/Core/Form/ConfigTarget.php
+++ b/core/lib/Drupal/Core/Form/ConfigTarget.php
@@ -9,18 +9,6 @@
  */
 final class ConfigTarget {
 
-  /**
-   * The name of the form element which maps to this config property.
-   *
-   * @var string
-   *
-   * @see \Drupal\Core\Form\ConfigFormBase::storeConfigKeyToFormElementMap()
-   *
-   * @internal
-   *   This property is for internal use only.
-   */
-  public string $elementName;
-
   /**
    * The parents of the form element which maps to this config property.
    *
diff --git a/core/modules/system/tests/modules/form_test/config/schema/form_test.schema.yml b/core/modules/system/tests/modules/form_test/config/schema/form_test.schema.yml
index 9af17cdd860c35fc16ae8ab9b65973d92dd2ced3..dccdc5bc93636e9d7cff4dbdfd82a8b8bcb8e5cc 100644
--- a/core/modules/system/tests/modules/form_test/config/schema/form_test.schema.yml
+++ b/core/modules/system/tests/modules/form_test/config/schema/form_test.schema.yml
@@ -5,3 +5,9 @@ form_test.object:
     bananas:
       type: string
       label: 'Bananas'
+    favorite_vegetable:
+      type: required_label
+      label: 'Favorite vegetable'
+    nemesis_vegetable:
+      type: required_label
+      label: 'Nemesis vegetable'
diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml
index 9c971a727e1dd81faaa268e7f6e515ca552e247d..fd4db9da386f76e549ff1100db996c8f532c3963 100644
--- a/core/modules/system/tests/modules/form_test/form_test.routing.yml
+++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml
@@ -528,3 +528,17 @@ form_test.javascript_states_form:
     _form: '\Drupal\form_test\Form\JavascriptStatesForm'
   requirements:
     _access: 'TRUE'
+
+form_test.tree_config_target:
+  path: '/form-test/tree-config-target'
+  defaults:
+    _form: '\Drupal\form_test\Form\TreeConfigTargetForm'
+  requirements:
+    _access: 'TRUE'
+
+form_test.incorrect_config_target:
+  path: '/form-test/incorrect-config-target'
+  defaults:
+    _form: '\Drupal\form_test\Form\IncorrectConfigTargetForm'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/form_test/src/Form/IncorrectConfigTargetForm.php b/core/modules/system/tests/modules/form_test/src/Form/IncorrectConfigTargetForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..5296edb9cb88ef98bb17b90618d624b1194df484
--- /dev/null
+++ b/core/modules/system/tests/modules/form_test/src/Form/IncorrectConfigTargetForm.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\form_test\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+class IncorrectConfigTargetForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['form_test.object'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'form_test_incorrect_config_target_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['missing_key'] = [
+      '#type' => 'textfield',
+      '#title' => t('Missing key'),
+      '#config_target' => 'form_test.object:does_not_exist',
+    ];
+    return parent::buildForm($form, $form_state);
+  }
+
+}
diff --git a/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php b/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..502fbd8f71300d990e6733dcc5c93049c06df119
--- /dev/null
+++ b/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\form_test\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+class TreeConfigTargetForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['form_test.object'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'form_test_tree_config_target_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['vegetables'] = [
+      '#type' => 'details',
+      '#tree' => TRUE,
+      '#input' => TRUE,
+      '#title' => t('Vegetable preferences'),
+    ];
+    $form['vegetables']['favorite'] = [
+      '#type' => 'textfield',
+      '#title' => t('Favorite'),
+      '#default_value' => 'Potato',
+      '#config_target' => 'form_test.object:favorite_vegetable',
+    ];
+    $form['vegetables']['nemesis'] = [
+      '#type' => 'textfield',
+      '#title' => t('Nemesis'),
+      '#default_value' => 'Broccoli',
+      '#config_target' => 'form_test.object:nemesis_vegetable',
+    ];
+    return parent::buildForm($form, $form_state);
+  }
+
+}
diff --git a/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php b/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5ab7a735956fc45e81f208bdb492ffb28ffb518a
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Form;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests forms using #config_target.
+ *
+ * @group Form
+ */
+class ConfigTargetTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['form_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Tests #config_target where #tree is set to TRUE.
+   */
+  public function testTree(): void {
+    $this->drupalGet('/form-test/tree-config-target');
+    $page = $this->getSession()->getPage();
+    $page->fillField('Favorite', '');
+    $page->pressButton('Save configuration');
+    $assert_session = $this->assertSession();
+    $assert_session->statusMessageContains('This value should not be blank.', 'error');
+    $assert_session->elementAttributeExists('named', ['field', 'Favorite'], 'aria-invalid');
+    $assert_session->elementAttributeNotExists('named', ['field', 'Nemesis'], 'aria-invalid');
+  }
+
+  /**
+   * Tests #config_target with an incorrect key.
+   */
+  public function testIncorrectKey(): void {
+    $this->drupalGet('/form-test/incorrect-config-target');
+    $page = $this->getSession()->getPage();
+    $page->pressButton('Save configuration');
+    $assert_session = $this->assertSession();
+    $assert_session->statusMessageContains('\'does_not_exist\' is not a supported key.', 'error');
+    $assert_session->elementAttributeExists('named', ['field', 'Missing key'], 'aria-invalid');
+  }
+
+}