diff --git a/core/includes/form.inc b/core/includes/form.inc
index 5d4a943..ea61906 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -8,6 +8,7 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Number;
use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Xss;
use Drupal\Component\Utility\Url;
use Drupal\Core\Database\Database;
use Drupal\Core\Language\Language;
@@ -2687,6 +2688,9 @@ function form_process_file($element) {
/**
* Returns HTML for a form element.
+ * Prepares variables for form element templates.
+ *
+ * Default template: form-element.html.twig.
*
* Each form element is wrapped in a DIV container having the following CSS
* classes:
@@ -2723,7 +2727,7 @@ function form_process_file($element) {
* but the parent element should have neither. Use this carefully because a
* field without an associated label can cause accessibility challenges.
*
- * @param $variables
+ * @param array $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #title_display, #description, #id, #required,
@@ -2731,8 +2735,8 @@ function form_process_file($element) {
*
* @ingroup themeable
*/
-function theme_form_element($variables) {
- $element = &$variables['element'];
+function template_preprocess_form_element(&$variables) {
+ $element = $variables['element'];
// This function is invoked as theme wrapper, but the rendered form element
// may not necessarily have been processed by form_builder().
@@ -2740,67 +2744,85 @@ function theme_form_element($variables) {
'#title_display' => 'before',
);
+ // If #title is not set, we don't display any label or required marker.
+ if (!isset($element['#title'])) {
+ $element['#title_display'] = 'none';
+ }
+
+ // If the element is required, a required marker is appended to the label.
+ $variables['required'] = array();
+ if (!empty($element['#required'])) {
+ $variables['required'] = array(
+ '#theme' => 'form_required_marker',
+ '#element' => $element,
+ );
+ }
+
+ $variables['label']['attributes'] = array();
+ // Style the label as class option to display inline with the element.
+ if ($element['#title_display'] == 'after') {
+ $variables['label']['attributes']['class'][] = 'option';
+ }
+ // Show label only to screen readers to avoid disruption in visual flows.
+ elseif ($element['#title_display'] == 'invisible') {
+ $variables['label']['attributes']['class'][] = 'visually-hidden';
+ }
+
+ if (!empty($element['#id'])) {
+ $variables['label']['attributes']['for'] = $element['#id'];
+ }
+
+ // Create an Attribute object from the label attributes.
+ if (!empty($variables['label']['attributes'])) {
+ $variables['label']['attributes'] = new Attribute($variables['label']['attributes']);
+ }
+
// Take over any #wrapper_attributes defined by the element.
// @todo Temporary hack for #type 'item'.
// @see http://drupal.org/node/1829202
+ $variables['attributes'] = array();
if (isset($element['#wrapper_attributes'])) {
- $attributes = $element['#wrapper_attributes'];
+ $variables['attributes'] = $element['#wrapper_attributes'];
}
+
// Add element #id for #type 'item'.
if (isset($element['#markup']) && !empty($element['#id'])) {
- $attributes['id'] = $element['#id'];
+ $variables['attributes']['id'] = $element['#id'];
}
+
// Add element's #type and #name as class to aid with JS/CSS selectors.
- $attributes['class'][] = 'form-item';
+ $variables['attributes']['class'][] = 'form-item';
if (!empty($element['#type'])) {
- $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
+ $variables['attributes']['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
}
if (!empty($element['#name'])) {
- $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
+ $variables['attributes']['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
}
// Add a class for disabled elements to facilitate cross-browser styling.
if (!empty($element['#attributes']['disabled'])) {
- $attributes['class'][] = 'form-disabled';
+ $variables['attributes']['class'][] = 'form-disabled';
}
- $output = '
\n";
-
- return $output;
+ $variables['label']['display'] = $element['#title_display'];
+ $variables['label']['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
+ $variables['children'] = $element['#children'];
}
/**
@@ -2821,59 +2843,6 @@ function theme_form_required_marker($variables) {
}
/**
- * Returns HTML for a form element label and required marker.
- *
- * Form element labels include the #title and a #required marker. The label is
- * associated with the element itself by the element #id. Labels may appear
- * before or after elements, depending on theme_form_element() and
- * #title_display.
- *
- * This function will not be called for elements with no labels, depending on
- * #title_display. For elements that have an empty #title and are not required,
- * this function will output no label (''). For required elements that have an
- * empty #title, this will output the required marker alone within the label.
- * The label will use the #id to associate the marker with the field that is
- * required. That is especially important for screenreader users to know
- * which field is required.
- *
- * @param $variables
- * An associative array containing:
- * - element: An associative array containing the properties of the element.
- * Properties used: #required, #title, #id, #value, #description.
- *
- * @ingroup themeable
- */
-function theme_form_element_label($variables) {
- $element = $variables['element'];
-
- // If title and required marker are both empty, output no label.
- if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
- return '';
- }
-
- // If the element is required, a required marker is appended to the label.
- $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : '';
-
- $title = filter_xss_admin($element['#title']);
-
- $attributes = array();
- // Style the label as class option to display inline with the element.
- if ($element['#title_display'] == 'after') {
- $attributes['class'] = 'option';
- }
- // Show label only to screen readers to avoid disruption in visual flows.
- elseif ($element['#title_display'] == 'invisible') {
- $attributes['class'] = 'visually-hidden';
- }
-
- if (!empty($element['#id'])) {
- $attributes['for'] = $element['#id'];
- }
-
- return '';
-}
-
-/**
* 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 bfd1aaf..49d374f 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2724,13 +2724,11 @@ function drupal_common_theme() {
),
'form_element' => array(
'render element' => 'element',
+ 'template' => 'form-element',
),
'form_required_marker' => array(
'render element' => 'element',
),
- 'form_element_label' => array(
- 'render element' => 'element',
- ),
'vertical_tabs' => array(
'render element' => 'element',
'template' => 'vertical-tabs',
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index d4c1039..5381860 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -233,7 +233,7 @@ function hook_queue_info_alter(&$queues) {
* - "#post_render": array of callables taking $children and $element.
* - "#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().
+ * displayed, see form_element.html.twig.
*
* @see hook_element_info_alter()
* @see system_element_info()
diff --git a/core/modules/system/templates/form-element.html.twig b/core/modules/system/templates/form-element.html.twig
new file mode 100644
index 0000000..85fc5f5
--- /dev/null
+++ b/core/modules/system/templates/form-element.html.twig
@@ -0,0 +1,60 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a form element.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - prefix: (optional) The form element prefix, may not be set.
+ * - suffix: (optional) The form element suffix, may not be set.
+ * - required: The required marker, or empty if the associated form element is
+ * not required.
+ * - label: A list of label properties containing:
+ * - title: The label's text.
+ * - attributes: A list of HTML attributes for the label.
+ * - display: Label display setting. It 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
+ * 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.
+ * - 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 where a visual
+ * label is not needed, such as a table of checkboxes where the row and
+ * column provide the context. The tooltip will include the title and
+ * required marker.
+ * - description: (optional) A list of description properties containing:
+ * - 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.
+ *
+ * @see template_preprocess_form_element()
+ *
+ * @ingroup themeable
+ */
+#}
+
+ {% if label.display in ['before', 'invisible'] and (label.title is not empty or required) %}
+
+ {% endif %}
+ {% if prefix is not empty %}
+ {{ prefix }}
+ {% endif %}
+ {{ children }}
+ {% if suffix is not empty %}
+ {{ suffix }}
+ {% endif %}
+ {% if label.display == 'after' and (label.title is not empty or required) %}
+
+ {% endif %}
+ {% if description.content %}
+