diff --git a/core/includes/form.inc b/core/includes/form.inc
index 90d47fb..652d7bc 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -1829,6 +1829,7 @@ function form_builder($form_id, &$element, &$form_state) {
'#required' => FALSE,
'#attributes' => array(),
'#title_display' => 'before',
+ '#description_display' => 'after',
);
// Special handling if we're on the top level form element.
@@ -4642,19 +4643,23 @@ function form_process_file($element) {
* $form structure and set via form_builder()).
* - form-disabled: Only set if the form element is #disabled.
*
- * In addition to the element itself, the DIV contains a label for the element
- * based on the optional #title_display property, and an optional #description.
+ * In addition to the element itself, the DIV contains the element's label
+ * consisting of the #title property and an optional #required marker. It also
+ * contains an optional #description. While by default, the label is displayed
+ * before the element and the description right after the element, this can be
+ * modified by the optional #title_display and #description_display properties.
*
* The optional #title_display property can have these values:
- * - before: The label is output before the element. This is the default.
- * The label includes the #title and the required marker, if #required.
- * - after: The label is output after the element. For example, this is used
+ * - before (default): The label is displayed before the element. The label
+ * includes the #title and the required marker, if #required.
+ * - after: The label is displayed after the element. For example, this is used
* for radio and checkbox #type elements as set in system_element_info().
* If the #title is empty but the field is #required, the label will
* contain only the required marker.
- * - invisible: Labels are critical for screen readers to enable them to
- * properly navigate through forms but can be visually distracting. This
- * property hides the label for everyone except screen readers.
+ * - invisible: While the label isn't displayed by visual browsers, it is
+ * available to screen readers which need it for properly navigating through
+ * forms. It is also available to validation functions that might set an error
+ * on the field that failed validation.
* - attribute: Set the title attribute on the element to create a tooltip
* but output no label element. This is supported only for checkboxes
* and radios in form_pre_render_conditional_form_element(). It is used
@@ -4667,13 +4672,25 @@ function form_process_file($element) {
* This can be useful in cases such as the password_confirm element, which
* creates children elements that have their own labels and required markers,
* but the parent element should have neither. Use this carefully because a
- * field without an associated label can cause accessibility challenges.
+ * field without an associated label can cause accessibility challenges. In most
+ * cases where displaying a title would be visually distracting, it is strongly
+ * recommended to supply a title but render it invisible or as attribute.
+ *
+ * The optional #description_display property can have these values:
+ * - before: The description is output before the element. This may be used for
+ * elements that occupy very much vertical screen space.
+ * - after (default): The description is output after the element.
+ * - invisible: While the description isn't displayed by visual browsers, it is
+ * available to screen readers.
+ *
+ * If the #description property is not set, the description will not be output,
+ * regardless of the #description_display value.
*
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
- * Properties used: #title, #title_display, #description, #id, #required,
- * #children, #type, #name.
+ * Properties used: #title, #title_display, #description,
+ * #description_display, #id, #required, #children, #type, #name.
*
* @ingroup themeable
*/
@@ -4684,6 +4701,7 @@ function theme_form_element($variables) {
// may not necessarily have been processed by form_builder().
$element += array(
'#title_display' => 'before',
+ '#description_display' => 'after',
);
// Take over any #wrapper_attributes defined by the element.
@@ -4710,41 +4728,36 @@ function theme_form_element($variables) {
}
$output = '
' . "\n";
- // If #title is not set, we don't display any label or required marker.
+ // If #title is not set, we don't even call theme_form_element_label.
if (!isset($element['#title'])) {
$element['#title_display'] = 'none';
}
- $prefix = isset($element['#field_prefix']) ? '
' . $element['#field_prefix'] . ' ' : '';
- $suffix = isset($element['#field_suffix']) ? '
' . $element['#field_suffix'] . '' : '';
+ // If #description is not set, we don't even call
+ // theme_form_element_description.
+ if (!isset($element['#description'])) {
+ $element['#description_display'] == 'none';
+ }
- switch ($element['#title_display']) {
- case 'before':
- case 'invisible':
- $output .= ' ' . theme('form_element_label', $variables);
- $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
- break;
+ if ($element['#title_display'] == 'before' || $element['#title_display'] == 'invisible') {
+ $output .= ' ' . theme('form_element_label', $variables);
+ }
+ if ($element['#description_display'] == 'before') {
+ $output .= "\n" . theme('form_element_description', $variables) . "\n";
+ }
- case 'after':
- $output .= ' ' . $prefix . $element['#children'] . $suffix;
- $output .= ' ' . theme('form_element_label', $variables) . "\n";
- break;
+ // Render the element's children.
+ $output .= isset($element['#field_prefix']) ? '
' . $element['#field_prefix'] . ' ' : ' ';
+ $output .= $element['#children'];
+ $output .= isset($element['#field_suffix']) ? '
' . $element['#field_suffix'] . '' : ' ';
- case 'none':
- case 'attribute':
- // Output no label and no required marker, only the children.
- $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
- break;
+ if ($element['#title_display'] == 'after') {
+ $output .= ' ' . theme('form_element_label', $variables);
}
-
- if (!empty($element['#description'])) {
- $attributes = array('class' => 'description');
- if (!empty($element['#id'])) {
- $attributes['id'] = $element['#id'] . '--description';
- }
- $output .= '
' . $element['#description'] . "
\n";
+ if ($element['#description_display'] == 'after' || $element['#description_display'] == 'invisible') {
+ $output .= "\n" . theme('form_element_description', $variables) . "\n";
}
- $output .= "
\n";
+ $output .= '' . "\n";
return $output;
}
@@ -4787,7 +4800,7 @@ function theme_form_required_marker($variables) {
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
- * Properties used: #required, #title, #id, #value, #description.
+ * Properties used: #id, #title, #title_display, #required.
*
* @ingroup themeable
*/
@@ -4824,6 +4837,45 @@ function theme_form_element_label($variables) {
}
/**
+ * Returns HTML for a form element description.
+ *
+ * The description is associated with the element itself by the element #id.
+ * Labels may appear before or after elements, depending on theme_form_element()
+ * and #description_display.
+ *
+ * This function will not be called for elements with no #description.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #id, #description, #description_display.
+ *
+ * @ingroup themeable
+ */
+function theme_form_element_description($variables) {
+ $element = $variables['element'];
+ // This is also used in the installer, pre-database setup.
+ $t = get_t();
+
+ // If description is empty, don't output anything.
+ if (!isset($element['#description']) || $element['#description'] === '') {
+ return '';
+ }
+
+ $attributes = array();
+ if ($element['#description_display'] == 'invisible') {
+ $attributes['class'] = 'element-invisible';
+ }
+ else {
+ $attributes['class'] = 'description';
+ }
+ if (!empty($element['#id'])) {
+ $attributes['id'] = $element['#id'] . '--description';
+ }
+ return '' . $t($element['#description']) . '
';
+}
+
+/**
* Sets a form element's class attribute.
*
* Adds 'required' and 'error' classes as needed.
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index ae8ec15..e1e7fd5 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -3294,6 +3294,9 @@ function drupal_common_theme() {
'form_element_label' => array(
'render element' => 'element',
),
+ 'form_element_description' => array(
+ 'render element' => 'element',
+ ),
'vertical_tabs' => array(
'render element' => 'element',
),
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index e64db93..6169ee8 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -187,6 +187,7 @@ public function form(array $form, array &$form_state) {
'#default_value' => !empty($node->name) ? $node->name : '',
'#weight' => -1,
'#description' => t('Leave blank for %anonymous.', array('%anonymous' => $user_config->get('anonymous'))),
+ '#description_display' => 'invisible',
);
$form['author']['date'] = array(
'#type' => 'textfield',
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 30ee8b7..336ad2d 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -287,6 +287,8 @@ function hook_queue_info_alter(&$queues) {
* - "#submit": array of callback functions taking $form and $form_state.
* - "#title_display": optional string indicating if and how #title should be
* displayed, see theme_form_element() and theme_form_element_label().
+ * - "#description_display": optional string indicating how the #description
+ * should be displayed, see theme_form_element().
*
* @see hook_element_info_alter()
* @see system_element_info()