The #datetime form element prior to version 8.7.0 (?) had a number of problems and bugs:
- The #datetime label used to be provided via an
<h4>, not a<label>, making it inaccessible. - Although there is a
datetime-wrapper.html.twigtemplate, it did not provide any wrapper element, making it impossible to use the#statessystem with #datetime form elements.
This has now been fixed. The label is always a <label> (with the appropriate "for" attribute). The datetime-wrapper.html.twig template now defines a wrapper element. The #states-related JavaScript code has been updated to work properly for #datetime elements.
However, fixing these bugs required changes to the markup provided by Drupal core. Our backwards compatibility policy states that such changes will not occur in the "Stable" and "Classy" themes. So if your site is using one of those themes, or a subtheme of either one, you must define your own datetime-wrapper.html.twig template. You can copy the template from core/modules/system/templates/datetime-wrapper.html.twig into your theme's templates directory, clear/rebuild your caches, and you should be all set.
If your theme is already overriding the datetime-wrapper.html.twig template, you will need to update your template to benefit from this fix.
Before: Classy's broken template
{#
/**
* @file
* Theme override of a datetime form wrapper.
*
* Available variables:
* - content: The form element to be output, usually a datelist, or datetime.
* - title: The title of the form element.
* - title_attributes: HTML attributes for the title wrapper.
* - description: Description text for the form element.
* - required: An indicator for whether the associated form element is required.
*
* @see template_preprocess_datetime_wrapper()
*/
#}
{%
set title_classes = [
'label',
required ? 'js-form-required',
required ? 'form-required',
]
%}
{% if title %}
<h4{{ title_attributes.addClass(title_classes) }}>{{ title }}</h4>
{% endif %}
{{ content }}
{% if errors %}
<div class="form-item--error-message">
<strong>{{ errors }}</strong>
</div>
{% endif %}
{% if description %}
<div{{ description_attributes.addClass('description') }}>
{{ description }}
</div>
{% endif %}
After: The default template provided by the Drupal core system module
{#
/**
* @file
* Default theme implementation of a datetime form wrapper.
*
* Available variables:
* - content: The form element to be output, usually a datelist, or datetime.
* - label: A rendered label element.
* - 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. 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.
* - description: Description text for the form element.
* - required: An indicator for whether the associated form element is required.
*
* @see template_preprocess_datetime_wrapper()
*
* @ingroup themeable
*/
#}
{%
set container_classes = [
'js-complex-form-item',
]
%}
<div{{ attributes.addClass(container_classes) }}>
{% if label_display in ['before', 'invisible'] %}
{{ label }}
{% endif %}
{{ content }}
{% if errors %}
<div>
{{ errors }}
</div>
{% endif %}
{% if description %}
<div{{ description_attributes }}>
{{ description }}
</div>
{% endif %}
{% if label_display == 'after' %}
{{ label }}
{% endif %}
</div>
Key differences
The main things to notice are:
- Even though the template is called "datetime-wrapper", Classy doesn't actually provide a wrapper element at all. You must have a wrapper element (e.g.
<div>) with thejs-complex-form-itemclass for the Form API#statessystem to work. - The form element label was being enclosed in a raw
<h4>tag, using the{{ title }}template variable. By using{{ label }}, the form element will be wrapped in a<label>tag with aforattribute pointing to the correct form element. For example, the resulting markup would look something like<label for="edit-date-time-">Date/time</label>which is much better for accessibility. - Classy's template does not honor the
label_displaysetting. Conditionally placing{{ label }}in your template based on the value oflabel_displayensures that this setting from Drupal core's Field API actually works.