diff --git a/core/includes/form.inc b/core/includes/form.inc index c3c11882039d7c3c7a282183ba225dd7d633ce7f..972fa3dd434b26aa6c7a7659265807054741583d 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -2714,7 +2714,11 @@ function template_preprocess_form_element(&$variables) { $variables['description'] = NULL; if (!empty($element['#description'])) { - $description_attributes = array('class' => 'description'); + $variables['description_display'] = $element['#description_display']; + $description_attributes = array('class' => array('description')); + if ($element['#description_display'] === 'invisible') { + $description_attributes['class'][] = 'visually-hidden'; + } if (!empty($element['#id'])) { $description_attributes['id'] = $element['#id'] . '--description'; } diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index ab1c1c57efb65ed9ba4e4bcfb640b7e40d734ddf..adbb8f335681932d49c7fd704521f8adbe27dac5 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -703,6 +703,7 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state '#required' => FALSE, '#attributes' => array(), '#title_display' => 'before', + '#description_display' => 'after', '#errors' => NULL, ); diff --git a/core/modules/system/src/Tests/Form/ElementsLabelsTest.php b/core/modules/system/src/Tests/Form/ElementsLabelsTest.php index 970f29943ca0dea57e276e7093e0c21cd2d2d1be..dbf13d2c86a1e44d7963630d5efff39e32f74e0c 100644 --- a/core/modules/system/src/Tests/Form/ElementsLabelsTest.php +++ b/core/modules/system/src/Tests/Form/ElementsLabelsTest.php @@ -94,4 +94,32 @@ function testFormLabels() { $elements = $this->xpath('//div[@id="edit-form-radios-title-attribute"]'); $this->assertEqual($elements[0]['title'], 'Radios test' . ' (' . t('Required') . ')', 'Title attribute found.'); } + + /** + * Tests different display options for form element descriptions. + */ + function testFormDescriptions() { + $this->drupalGet('form_test/form-descriptions'); + + // 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.')); + + // 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.')); + + // Check if the class is 'visually-hidden' on the form element description + // for the option with #description_display='invisible' and also check that + // the description is placed after the form element. + $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.')); + } + } diff --git a/core/modules/system/templates/form-element.html.twig b/core/modules/system/templates/form-element.html.twig index ea4d90f63f040d8393a4622335a9422563be0f1e..e029b18901c26f49b5f5aff5c10714238da1b922 100644 --- a/core/modules/system/templates/form-element.html.twig +++ b/core/modules/system/templates/form-element.html.twig @@ -30,6 +30,12 @@ * - content: A description of the form element, may not be set. * - attributes: (optional) A list of HTML attributes to apply to the * description content wrapper. Will only be set when description is set. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element. This is the default + * value. + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * * @see template_preprocess_form_element() * @@ -43,6 +49,11 @@ {% if prefix is not empty %} <span class="field-prefix">{{ prefix }}</span> {% endif %} + {% if description_display == 'before' and description.content %} + <div{{ description.attributes }}> + {{ description.content }} + </div> + {% endif %} {{ children }} {% if suffix is not empty %} <span class="field-suffix">{{ suffix }}</span> @@ -50,7 +61,7 @@ {% if label_display == 'after' %} {{ label }} {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes }}> {{ description.content }} </div> 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 86d7fc0938c6c02bfb921e4d1f4aec5433d2369e..109d8f37c3ea654b988340fc44f5830db112fe9e 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 @@ -378,6 +378,14 @@ form_test.button_class: requirements: _access: 'TRUE' +form_test.description_display: + path: '/form_test/form-descriptions' + defaults: + _form: '\Drupal\form_test\Form\FormTestDescriptionForm' + _title: 'Form description test' + requirements: + _access: 'TRUE' + form_test.group_details: path: '/form-test/group-details' defaults: 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 new file mode 100644 index 0000000000000000000000000000000000000000..aeb11964253ceb309bc3f94f7e257d437201703c --- /dev/null +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestDescriptionForm.php @@ -0,0 +1,62 @@ +<?php + +/** + * @file + * Contains \Drupal\form_test\Form\FormTestEmailForm. + */ + +namespace Drupal\form_test\Form; + +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; + +/** + * Defines a form for testing form element description display options. + * + * @see \Drupal\system\Tests\Form\ElementsLabelsTest::testFormDescriptions() + */ +class FormTestDescriptionForm extends FormBase { + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'form_test_description_display'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['form_textfield_test_description_before'] = array( + '#type' => 'textfield', + '#title' => 'Textfield test for description before element', + '#description' => 'Textfield test for description before element', + '#description_display' => 'before', + ); + + $form['form_textfield_test_description_after'] = array( + '#type' => 'textfield', + '#title' => 'Textfield test for description after element', + '#description' => 'Textfield test for description after element', + '#description_display' => 'after', + ); + + $form['form_textfield_test_description_invisible'] = array( + '#type' => 'textfield', + '#title' => 'Textfield test for visually-hidden description', + '#description' => 'Textfield test for visually-hidden description', + '#description_display' => 'invisible', + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + // The test that uses this form does not submit the form so this is empty. + } + +}