diff --git a/core/includes/form.inc b/core/includes/form.inc
index 8170820..6d080f6 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -236,9 +236,22 @@ function template_preprocess_details(&$variables) {
$variables['summary_attributes']['aria-pressed'] = $variables['summary_attributes']['aria-expanded'];
}
$variables['title'] = (!empty($element['#title'])) ? $element['#title'] : '';
- $variables['description'] = (!empty($element['#description'])) ? $element['#description'] : '';
$variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
$variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
+
+ $variables['description'] = NULL;
+ if (!empty($element['#description'])) {
+ $variables['description_display'] = isset($element['#description_display']) ? $element['#description_display'] : 'before';
+ $description_attributes = [];
+ if ($variables['description_display'] === 'invisible') {
+ $description_attributes['class'][] = 'visually-hidden';
+ }
+ if (!empty($element['#id'])) {
+ $description_attributes['id'] = $element['#id'] . '--description';
+ }
+ $variables['description']['attributes'] = new Attribute($description_attributes);
+ $variables['description']['content'] = $element['#description'];
+ }
}
/**
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 2e0b192..86163d6 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -755,11 +755,12 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
$element['#defaults_loaded'] = TRUE;
}
// Assign basic defaults common for all form elements.
+ // NOTE: description_display is below by default, excepted for details.
$element += array(
'#required' => FALSE,
'#attributes' => array(),
'#title_display' => 'before',
- '#description_display' => 'after',
+ '#description_display' => (isset($element['#type']) && $element['#type'] == 'details') ? 'before' : 'after',
'#errors' => NULL,
);
diff --git a/core/modules/system/src/Tests/Form/ElementsDetailsTest.php b/core/modules/system/src/Tests/Form/ElementsDetailsTest.php
new file mode 100644
index 0000000..358b299
--- /dev/null
+++ b/core/modules/system/src/Tests/Form/ElementsDetailsTest.php
@@ -0,0 +1,58 @@
+drupalGet('form-test/group-details');
+
+ // Check #description default placement.
+ $field_id = 'edit-meta';
+ $description_id = $field_id . '--description';
+ $elements = $this->xpath('//details[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]//div/preceding-sibling::div');
+ $this->assertTrue(isset($elements[0]), t('Properly places by default the #description element before the form item within details group.'));
+
+ // Check #description placement with #description_display='before'.
+ $field_id = 'edit-details-before';
+ $description_id = $field_id . '--description';
+ $elements = $this->xpath('//details[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]//div[@id="edit-meta-before"]/preceding-sibling::div[@id="' . $description_id . '"]');
+ $this->assertTrue(isset($elements[0]), t('Properly places the #description element before the form item within details group.'));
+
+ // Check #description placement with #description_display='after'.
+ $field_id = 'edit-details-after';
+ $description_id = $field_id . '--description';
+ $elements = $this->xpath('//details[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]//div[@id="edit-meta-after"]/following-sibling::div[@id="' . $description_id . '"]');
+ $this->assertTrue(isset($elements[0]), t('Properly places the #description element after the form item within details group.'));
+
+ // Check if the class is 'visually-hidden' on the form details description
+ // for the option with #description_display='invisible' and also check that
+ // the description is placed after the form element.
+ $field_id = 'edit-details-invisible';
+ $description_id = $field_id . '--description';
+ $elements = $this->xpath('//details[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]//div[@id="edit-meta-invisible"]/following-sibling::div[contains(@class, "visually-hidden")]');
+ $this->assertTrue(isset($elements[0]), t('Properly renders the #description element visually-hidden within details group.'));
+ }
+}
diff --git a/core/modules/system/templates/details.html.twig b/core/modules/system/templates/details.html.twig
index 1c0fd79..7ed4bca 100644
--- a/core/modules/system/templates/details.html.twig
+++ b/core/modules/system/templates/details.html.twig
@@ -7,6 +7,12 @@
* - attributes: A list of HTML attributes for the details element.
* - title: (optional) The title of the element, may not be set.
* - description: (optional) The description of the element, may not be set.
+ * - description_display: (optional) Description display setting. It can have these values:
+ * - before: The description is output before the element. This is the default
+ * value.
+ * - after: The description is output after the element.
+ * - invisible: The description is output after the element, hidden visually
+ * but available to screen readers.
* - children: (optional) The children of the element, may not be set.
* - value: (optional) The value of the element, may not be set.
*
@@ -20,7 +26,12 @@