diff --git a/core/includes/form.inc b/core/includes/form.inc
index 05f2a9e..15ef023 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -240,6 +240,8 @@ function template_preprocess_fieldset(&$variables) {
  */
 function template_preprocess_details(&$variables) {
   $element = $variables['element'];
+  Element::setAttributes($element, ['id']);
+  Element\RenderElement::setAttributes($element);
   $variables['attributes'] = $element['#attributes'];
   $variables['summary_attributes'] = new Attribute();
   if (!empty($element['#title'])) {
@@ -251,7 +253,18 @@ 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'] : '';
+
+  if (!empty($element['#description'])) {
+    $variables['description_display'] = isset($element['#description_display']) ? $element['#description_display'] : 'before';
+    $description_id = $element['#attributes']['id'] . '--description';
+    $description_attributes['id'] = $description_id;
+    $variables['description']['attributes'] = new Attribute($description_attributes);
+    $variables['description']['content'] = $element['#description'];
+
+    // Add the description's id to the fieldset aria attributes.
+    $variables['attributes']['aria-describedby'] = $description_id;
+  }
+
   $variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
   $variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
   $variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index c4a1fd2..2c36a22 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -901,11 +901,12 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       $element['#defaults_loaded'] = TRUE;
     }
     // Assign basic defaults common for all form elements.
+    // Note that #description_display is below by default, except for details.
     $element += [
       '#required' => FALSE,
       '#attributes' => [],
       '#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..48c3bae
--- /dev/null
+++ b/core/modules/system/src/Tests/Form/ElementsDetailsTest.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\system\Tests\Form;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests details element rendering and description placement.
+ *
+ * @group Form
+ */
+class ElementsDetailsTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['form_test'];
+
+  /**
+   * Tests different display options for details element descriptions.
+   */
+  public function testFieldsetDescriptions() {
+    $this->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]), '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]), '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]), '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]), 'Properly renders the #description element visually-hidden within details group.');
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Form/ElementsLabelsTest.php b/core/modules/system/src/Tests/Form/ElementsLabelsTest.php
index 2a57e5d..01254a8 100644
--- a/core/modules/system/src/Tests/Form/ElementsLabelsTest.php
+++ b/core/modules/system/src/Tests/Form/ElementsLabelsTest.php
@@ -102,17 +102,24 @@ public function testFormLabels() {
   public function testFormDescriptions() {
     $this->drupalGet('form_test/form-descriptions');
 
+    // Check #description placement with no #description_display, so default is
+    // 'after' the form item.
+    $field_id = 'edit-form-textfield-test-description-default';
+    $description_id = $field_id . '--description';
+    $elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/following-sibling::div[@id="' . $description_id . '"]');
+    $this->assertTrue(isset($elements[0]), 'Properly places by default the #description element after the form item.');
+
     // Check #description placement with #description_display='after'.
     $field_id = 'edit-form-textfield-test-description-after';
     $description_id = $field_id . '--description';
     $elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/following-sibling::div[@id="' . $description_id . '"]');
-    $this->assertTrue(isset($elements[0]), t('Properly places the #description element after the form item.'));
+    $this->assertTrue(isset($elements[0]), 'Properly places the #description element after the form item.');
 
     // Check #description placement with #description_display='before'.
     $field_id = 'edit-form-textfield-test-description-before';
     $description_id = $field_id . '--description';
     $elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/preceding-sibling::div[@id="' . $description_id . '"]');
-    $this->assertTrue(isset($elements[0]), t('Properly places the #description element before the form item.'));
+    $this->assertTrue(isset($elements[0]), 'Properly places the #description element before the form item.');
 
     // Check if the class is 'visually-hidden' on the form element description
     // for the option with #description_display='invisible' and also check that
@@ -120,7 +127,7 @@ public function testFormDescriptions() {
     $field_id = 'edit-form-textfield-test-description-invisible';
     $description_id = $field_id . '--description';
     $elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/following-sibling::div[contains(@class, "visually-hidden")]');
-    $this->assertTrue(isset($elements[0]), t('Properly renders the #description element visually-hidden.'));
+    $this->assertTrue(isset($elements[0]), 'Properly renders the #description element visually-hidden.');
   }
 
   /**
diff --git a/core/modules/system/templates/details.html.twig b/core/modules/system/templates/details.html.twig
index 5014deb..298f2ad 100644
--- a/core/modules/system/templates/details.html.twig
+++ b/core/modules/system/templates/details.html.twig
@@ -8,6 +8,13 @@
  * - errors: (optional) Any errors for this details element, may not be set.
  * - 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.
  *
@@ -16,6 +23,12 @@
  * @ingroup themeable
  */
 #}
+{%
+  set description_classes = [
+    'description',
+    description_display == 'invisible' ? 'visually-hidden',
+  ]
+%}
 <details{{ attributes }}>
   {%
     set summary_classes = [
@@ -33,7 +46,18 @@
     </div>
   {% endif %}
 
-  {{ description }}
+  {% if description_display == 'before' and description.content %}
+    <div{{ description.attributes.addClass(description_classes) }}>
+      {{ description.content }}
+    </div>
+  {% endif %}
+
   {{ children }}
   {{ value }}
+
+  {% if description_display in ['after', 'invisible'] and description.content %}
+    <div{{ description.attributes.addClass(description_classes) }}>
+      {{ description.content }}
+    </div>
+  {% endif %}
 </details>
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestDescriptionForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestDescriptionForm.php
index 81120ad..bb8286b 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestDescriptionForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestDescriptionForm.php
@@ -23,6 +23,12 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['form_textfield_test_description_default'] = [
+      '#type' => 'textfield',
+      '#title' => 'Textfield test for description default after element',
+      '#description' => 'Textfield test for description default after element',
+    ];
+
     $form['form_textfield_test_description_before'] = [
       '#type' => 'textfield',
       '#title' => 'Textfield test for description before element',
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php
index 002b07d..304edf4 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php
@@ -30,6 +30,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $required
     $form['meta'] = [
       '#type' => 'details',
       '#title' => 'Group element',
+      '#description' => 'Details test for default description position.',
       '#open' => TRUE,
       '#group' => 'details',
     ];
@@ -37,6 +38,55 @@ public function buildForm(array $form, FormStateInterface $form_state, $required
       '#type' => 'textfield',
       '#title' => 'Nest in details element',
     ];
+
+    $form['details_before'] = [
+      '#type' => 'details',
+      '#title' => 'Details test for description before element',
+      '#description' => 'Details test for description before element.',
+      '#description_display' => 'before',
+    ];
+    $form['meta_before'] = [
+      '#type' => 'container',
+      '#title' => 'Group element',
+      '#group' => 'details_before',
+    ];
+    $form['meta_before']['element'] = [
+      '#type' => 'textfield',
+      '#title' => 'Nest in container element',
+    ];
+
+    $form['details_after'] = [
+      '#type' => 'details',
+      '#title' => 'Details test for description after element',
+      '#description' => 'Details test for description after element.',
+      '#description_display' => 'after',
+    ];
+    $form['meta_after'] = [
+      '#type' => 'container',
+      '#title' => 'Group element',
+      '#group' => 'details_after',
+    ];
+    $form['meta_after']['element'] = [
+      '#type' => 'textfield',
+      '#title' => 'Nest in container element',
+    ];
+
+    $form['details_invisible'] = [
+      '#type' => 'details',
+      '#title' => 'Details test for visually-hidden description',
+      '#description' => 'Details test for visually-hidden description.',
+      '#description_display' => 'invisible',
+    ];
+    $form['meta_invisible'] = [
+      '#type' => 'container',
+      '#title' => 'Group element',
+      '#group' => 'details_invisible',
+    ];
+    $form['meta_invisible']['element'] = [
+      '#type' => 'textfield',
+      '#title' => 'Nest in container element',
+    ];
+
     return $form;
   }
 
diff --git a/core/themes/classy/templates/form/details.html.twig b/core/themes/classy/templates/form/details.html.twig
index 24366c3..2a9669b 100644
--- a/core/themes/classy/templates/form/details.html.twig
+++ b/core/themes/classy/templates/form/details.html.twig
@@ -8,12 +8,25 @@
  * - errors: (optional) Any errors for this details element, may not be set.
  * - 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.
  *
  * @see template_preprocess_details()
  */
 #}
+{%
+  set description_classes = [
+    'description',
+    description_display == 'invisible' ? 'visually-hidden',
+  ]
+%}
 <details{{ attributes }}>
   {%- if title -%}
     {%
@@ -30,14 +43,21 @@
         <strong>{{ errors }}</strong>
       </div>
     {% endif %}
-    {%- if description -%}
-      <div class="details-description">{{ description }}</div>
-    {%- endif -%}
+    {% if description_display == 'before' and description.content %}
+      <div{{ description.attributes.addClass(description_classes) }}>
+        {{ description.content }}
+      </div>
+    {% endif %}
     {%- if children -%}
       {{ children }}
     {%- endif -%}
     {%- if value -%}
       {{ value }}
     {%- endif -%}
+    {% if description_display in ['after', 'invisible'] and description.content %}
+      <div{{ description.attributes.addClass(description_classes) }}>
+        {{ description.content }}
+      </div>
+    {% endif %}
   </div>
 </details>
diff --git a/core/themes/stable/templates/form/details.html.twig b/core/themes/stable/templates/form/details.html.twig
index ccd2796..0484677 100644
--- a/core/themes/stable/templates/form/details.html.twig
+++ b/core/themes/stable/templates/form/details.html.twig
@@ -31,7 +31,7 @@
     </div>
   {% endif %}
 
-  {{ description }}
+  {{ description.content }}
   {{ children }}
   {{ value }}
 </details>
