diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index d2d5497..48e6ab9 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -574,27 +574,66 @@ function template_preprocess_datetime_form(&$variables) {
  */
 function template_preprocess_datetime_wrapper(&$variables) {
   $element = $variables['element'];
+  $element += [
+    '#title_display' => 'before',
+    '#wrapper_attributes' => [],
+    '#label_attributes' => [],
+  ];
+  $variables['attributes'] = $element['#wrapper_attributes'];
 
-  if (!empty($element['#title'])) {
-    $variables['title'] = $element['#title'];
+  $variables['label_display'] = $element['#title_display'];
+  if (isset($element['#title']) && $element['#title'] !== '') {
     // If the element title is a string, wrap it a render array so that markup
     // will not be escaped (but XSS-filtered).
-    if (is_string($variables['title']) && $variables['title'] !== '') {
-      $variables['title'] = ['#markup' => $variables['title']];
+    if (is_string($element['#title'])) {
+      $variables['legend']['title'] = ['#markup' => $element['#title']];
+    }
+    else {
+      $variables['legend']['title'] = $element['#title'];
     }
   }
 
+  $variables['legend']['#attributes'] = $element['#label_attributes'] ?? new Attribute();
+  // Add 'visually-hidden' class to legend span.
+  if ($variables['label_display'] === 'invisible') {
+    $variables['legend_span']['attributes'] = new Attribute(['class' => ['visually-hidden']]);
+  }
+  else {
+    $variables['legend_span']['attributes'] = new Attribute();
+  }
+
+  // Pass elements #type and #name to template.
+  if (!empty($element['#type'])) {
+    $variables['type'] = $element['#type'];
+  }
+  if (!empty($element['#name'])) {
+    $variables['name'] = $element['#name'];
+  }
+
   // Suppress error messages.
   $variables['errors'] = NULL;
 
   $variables['description'] = NULL;
   if (!empty($element['#description'])) {
-    $description_attributes = [];
     if (!empty($element['#id'])) {
-      $description_attributes['id'] = $element['#id'] . '--description';
+      $description_id = $element['#id'] . '--description';
     }
-    $variables['description'] = $element['#description'];
+    elseif (!empty($element['#attributes']['id'])) {
+      $description_id = $element['#attributes']['id'] . '--description';
+    }
+    $description_attributes['id'] = $description_id;
     $variables['description_attributes'] = new Attribute($description_attributes);
+    $variables['description'] = $element['#description'];
+
+    // Add the description's id to the fieldset aria attributes.
+    $variables['attributes']['aria-describedby'] = $description_id;
+  }
+
+  // For disabled datetime fields, the 'disabled' attribute should not be
+  // applied to the wrapper, but the 'form-disabled' class should be.
+  $variables['disabled'] = !empty($element['#attributes']['disabled']) ? $element['#attributes']['disabled'] : NULL;
+  if (isset($variables['attributes']['disabled'])) {
+    unset($variables['attributes']['disabled']);
   }
 
   $variables['required'] = FALSE;
diff --git a/core/modules/system/templates/datetime-wrapper.html.twig b/core/modules/system/templates/datetime-wrapper.html.twig
index a14da89..bf3681d 100644
--- a/core/modules/system/templates/datetime-wrapper.html.twig
+++ b/core/modules/system/templates/datetime-wrapper.html.twig
@@ -5,8 +5,16 @@
  *
  * 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.
+ * - 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.
  *
@@ -15,23 +23,37 @@
  * @ingroup themeable
  */
 #}
+
 {%
-  set title_classes = [
+  set legend_span_classes = [
+    'fieldset-legend',
     required ? 'js-form-required',
     required ? 'form-required',
   ]
 %}
-{% if title %}
-  <h4{{ title_attributes.addClass(title_classes) }}>{{ title }}</h4>
-{% endif %}
-{{ content }}
-{% if errors %}
-  <div>
-    {{ errors }}
-  </div>
-{% endif %}
-{% if description %}
-  <div{{ description_attributes }}>
-    {{ description }}
-  </div>
-{% endif %}
+<fieldset{{ attributes }}>
+  {% if label_display in ['before', 'invisible'] %}
+    {# Always wrap fieldset legends in a <span> for CSS positioning. #}
+    {# @todo This should be a single twig variable / render array for reuse. #}
+    <legend{{ legend.attributes }}>
+      <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
+    </legend>
+  {% endif %}
+  {{ content }}
+  {% if errors %}
+    <div>
+      {{ errors }}
+    </div>
+  {% endif %}
+  {% if description %}
+    <div{{ description_attributes }}>
+      {{ description }}
+    </div>
+  {% endif %}
+  {% if label_display == 'after' %}
+    {# @todo This should be a single twig variable / render array for reuse. #}
+    <legend{{ legend.attributes }}>
+      <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
+    </legend>
+  {% endif %}
+</fieldset>
diff --git a/core/themes/seven/templates/datetime-wrapper.html.twig b/core/themes/seven/templates/datetime-wrapper.html.twig
new file mode 100644
index 0000000..5089c01
--- /dev/null
+++ b/core/themes/seven/templates/datetime-wrapper.html.twig
@@ -0,0 +1,68 @@
+{#
+/**
+ * @file
+ * Theme override 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()
+ */
+#}
+{%
+  set container_classes = [
+    'js-form-item',
+    'form-item',
+    'js-form-wrapper',
+    'form-wrapper',
+    'js-form-type-' ~ type|clean_class,
+    'form-type-' ~ type|clean_class,
+    'js-form-item-' ~ name|clean_class,
+    'form-item-' ~ name|clean_class,
+   disabled == 'disabled' ? 'form-disabled',
+  ]
+%}
+{%
+  set legend_span_classes = [
+    'fieldset-legend',
+    required ? 'js-form-required',
+    required ? 'form-required',
+  ]
+%}
+<fieldset{{ attributes.addClass(container_classes) }}>
+  {% if label_display in ['before', 'invisible'] %}
+    {# Always wrap fieldset legends in a <span> for CSS positioning. #}
+    {# @todo This should be a single twig variable / render array for reuse. #}
+    <legend{{ legend.attributes }}>
+      <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
+    </legend>
+  {% 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 %}
+  {% if label_display == 'after' %}
+    <legend{{ legend.attributes }}>
+      <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
+    </legend>
+  {% endif %}
+</fieldset>
