From b38c7301939e201a17b9739ec9b64decd95990fc Mon Sep 17 00:00:00 2001 From: Stephen Mulvihill Date: Mon, 8 Sep 2025 17:53:16 -0700 Subject: [PATCH 1/5] Fix required checkbox rendering so label gets proper styles, add hook_preprocess_form_element to set required market for twig --- templates/input/form-element-label.html.twig | 2 +- templates/input/form-element.html.twig | 3 ++- wxt_bootstrap.theme | 9 +++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/templates/input/form-element-label.html.twig b/templates/input/form-element-label.html.twig index ff59587..d8ec357 100644 --- a/templates/input/form-element-label.html.twig +++ b/templates/input/form-element-label.html.twig @@ -25,7 +25,7 @@ title_display == 'invisible' and not (is_checkbox or is_radio) ? 'sr-only', required ? 'js-form-required', required ? 'form-required', - required ? 'required' + required and not (is_checkbox or is_radio) ? 'required' ] -%} {% if title is not empty and title_display == 'invisible' and (is_checkbox or is_radio) -%} diff --git a/templates/input/form-element.html.twig b/templates/input/form-element.html.twig index 8e2cbd7..692bf81 100644 --- a/templates/input/form-element.html.twig +++ b/templates/input/form-element.html.twig @@ -61,7 +61,8 @@ is_radio ? 'radio', is_checkbox ? 'checkbox', is_autocomplete ? 'form-autocomplete', - has_error ? 'error has-error' + has_error ? 'error has-error', + required ? 'required', ] %}{% set description_classes = [ diff --git a/wxt_bootstrap.theme b/wxt_bootstrap.theme index db40f02..bd4125c 100644 --- a/wxt_bootstrap.theme +++ b/wxt_bootstrap.theme @@ -338,3 +338,12 @@ function wxt_bootstrap_preprocess_paragraph(&$variables) { $variables['link_provider_label'] = $allowed_values[$key] ?? $key; } } + +/** + * Implements hook_preprocess_form_element(). + */ +function wxt_bootstrap_preprocess_form_element(array &$variables) { + if (!empty($variables['element']['#required'])) { + $variables['required'] = TRUE; + } +} From 5015ba836798d0a94218da3e61dba4fdb87deca7 Mon Sep 17 00:00:00 2001 From: Stephen Mulvihill Date: Mon, 8 Sep 2025 18:10:13 -0700 Subject: [PATCH 2/5] Fix required checkboxes and radios to show required markers when required --- templates/system/fieldset.html.twig | 79 +++++++++++++++++++++++++++++ wxt_bootstrap.theme | 18 +++++++ 2 files changed, 97 insertions(+) create mode 100644 templates/system/fieldset.html.twig diff --git a/templates/system/fieldset.html.twig b/templates/system/fieldset.html.twig new file mode 100644 index 0000000..955958b --- /dev/null +++ b/templates/system/fieldset.html.twig @@ -0,0 +1,79 @@ +{# +/** + * @file + * Default theme implementation for a fieldset element and its children. + * + * Available variables: + * - attributes: HTML attributes for the
element. + * - errors: (optional) Any errors for this
element, may not be set. + * - required: Boolean indicating whether the
element is required. + * - legend: The element containing the following properties: + * - title: Title of the
, intended for use as the text + of the . + * - attributes: HTML attributes to apply to the element. + * - description: The description element containing the following properties: + * - content: The description content of the
. + * - attributes: HTML attributes to apply to the description container. + * - 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 (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. + * - children: The rendered child elements of the
. + * - prefix: The content to add before the
children. + * - suffix: The content to add after the
children. + * + * @see template_preprocess_fieldset() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + 'js-form-item', + 'form-item', + 'js-form-wrapper', + 'form-wrapper', + ] +%} + + {% + set legend_span_classes = [ + 'fieldset-legend', + required ? 'js-form-required', + required ? 'form-required', + ] + %} + {% + set legend_classes = [ + required ? 'required', + ] + %} + {# Always wrap fieldset legends in a for CSS positioning. #} + + {{ legend.title }} + {% if required %} + ({{ 'required'|t }}) + {% endif %} + +
+ {% if description_display == 'before' and description.content %} + {{ description.content }}
+ {% endif %} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {% if prefix %} + {{ prefix }} + {% endif %} + {{ children }} + {% if suffix %} + {{ suffix }} + {% endif %} + {% if description_display in ['after', 'invisible'] and description.content %} + {{ description.content }} + {% endif %} + +
diff --git a/wxt_bootstrap.theme b/wxt_bootstrap.theme index bd4125c..43ec381 100644 --- a/wxt_bootstrap.theme +++ b/wxt_bootstrap.theme @@ -347,3 +347,21 @@ function wxt_bootstrap_preprocess_form_element(array &$variables) { $variables['required'] = TRUE; } } + +/** + * Implements hook_preprocess_fieldset(). + */ +function wxt_bootstrap_preprocess_fieldset(array &$variables) { + $element = $variables['element'] ?? []; + $checkbox_radio_group = [ + 'radios', + 'checkboxes', + ]; + + if (in_array($element['#type'], $checkbox_radio_group)) { + // Fieldset wrapper uses {{ attributes }} in Twig, so add the class here. + if (isset($variables['attributes'])) { + $variables['attributes']['class'][] = 'chkbxrdio-grp'; + } + } +} From 0edc87c3baaa7c1802887c242eb84f9fbcfa456f Mon Sep 17 00:00:00 2001 From: Stephen Mulvihill Date: Mon, 8 Sep 2025 20:05:30 -0700 Subject: [PATCH 3/5] Fix label rendering for single checkbox field --- templates/input/form-element.html.twig | 11 ++++++++++- wxt_bootstrap.theme | 14 +++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/templates/input/form-element.html.twig b/templates/input/form-element.html.twig index 692bf81..eb9917e 100644 --- a/templates/input/form-element.html.twig +++ b/templates/input/form-element.html.twig @@ -57,9 +57,9 @@ 'js-form-item-' ~ name|clean_class, title_display not in ['after', 'before'] ? 'form-no-label', disabled == 'disabled' ? 'form-disabled', - is_form_group ? 'form-group', is_radio ? 'radio', is_checkbox ? 'checkbox', + is_single_checkbox ? 'checkbox-standalone', is_autocomplete ? 'form-autocomplete', has_error ? 'error has-error', required ? 'required', @@ -71,6 +71,11 @@ description_display == 'invisible' ? 'visually-hidden', ] %} + +{% if is_form_group %} +
+{% endif %} + {% if label_display in ['before', 'invisible'] %} {{ label }} @@ -100,3 +105,7 @@
{% endif %} + +{% if is_form_group %} + +{% endif %} diff --git a/wxt_bootstrap.theme b/wxt_bootstrap.theme index 43ec381..d758765 100644 --- a/wxt_bootstrap.theme +++ b/wxt_bootstrap.theme @@ -343,9 +343,20 @@ function wxt_bootstrap_preprocess_paragraph(&$variables) { * Implements hook_preprocess_form_element(). */ function wxt_bootstrap_preprocess_form_element(array &$variables) { + // Mirror #required from the render array to Twig's $required so the template + // can add required classes/markers (used by GCWeb/WET and legend styling). if (!empty($variables['element']['#required'])) { $variables['required'] = TRUE; } + + // Webform context: if this is the single "checkbox" plugin, force Bootstrap's + // form-group wrapper and flag it as a single checkbox so spacing/markup match + if (!empty($variables['element']['#webform_plugin_id'])) { + if ($variables['element']['#webform_plugin_id'] == 'checkbox') { + $variables['is_form_group'] = TRUE; + $variables['is_single_checkbox'] = TRUE; + } + } } /** @@ -358,8 +369,9 @@ function wxt_bootstrap_preprocess_fieldset(array &$variables) { 'checkboxes', ]; + // When the fieldset is a radios/checkboxes group, add a marker class so + // WET/GCWeb can style spacing/legend consistently. if (in_array($element['#type'], $checkbox_radio_group)) { - // Fieldset wrapper uses {{ attributes }} in Twig, so add the class here. if (isset($variables['attributes'])) { $variables['attributes']['class'][] = 'chkbxrdio-grp'; } From 49b4ffd83740fea25ed72bb4bdcacbf6f9c8fcb6 Mon Sep 17 00:00:00 2001 From: Stephen Mulvihill Date: Mon, 20 Oct 2025 14:26:37 -0700 Subject: [PATCH 4/5] Add class field-name to fieldset legend to get field name in wet-boew validation summary --- templates/system/fieldset.html.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/system/fieldset.html.twig b/templates/system/fieldset.html.twig index 955958b..c3dc235 100644 --- a/templates/system/fieldset.html.twig +++ b/templates/system/fieldset.html.twig @@ -39,6 +39,7 @@ {% set legend_span_classes = [ + 'field-name', 'fieldset-legend', required ? 'js-form-required', required ? 'form-required', From a6496e76d6da8e95a352ebe263400b8757008de4 Mon Sep 17 00:00:00 2001 From: Stephen Mulvihill Date: Tue, 21 Oct 2025 08:43:37 -0700 Subject: [PATCH 5/5] Add group class and data-rule-require_form_group attribute to checkboxes in group to fix wet-boew/JQV treatment of grouped checkboxes --- wxt_bootstrap.theme | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/wxt_bootstrap.theme b/wxt_bootstrap.theme index d758765..bc0dc84 100644 --- a/wxt_bootstrap.theme +++ b/wxt_bootstrap.theme @@ -23,6 +23,7 @@ * @see \Drupal\bootstrap\Registry */ +use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormStateInterface; use Drupal\file\Entity\File; use Drupal\image\Entity\ImageStyle; @@ -213,6 +214,29 @@ function wxt_bootstrap_preprocess_input(&$variables) { else { $variables['search_submit'] = 'false'; } + + // Only for checkboxes. + $type_attr = $variables['attributes']['type'] ?? NULL; + $type = $type_attr ? (string) $type_attr->value() : NULL; + + if ($type == 'checkbox') { + // We need inputs that look like name="base[option]". + $name_attr = $variables['attributes']['name'] ?? NULL; + $name = $name_attr ? (string) $name_attr->value() : NULL; + if (!$name || !preg_match('/^([^\\[]+)\\[.+\\]$/', $name, $m)) { + return; + } + + $base = $m[1]; + // Build a stable, CSS-safe class per base name. + $group_class = 'js-wet-group-' . Html::getClass($base); + $selector = '.' . $group_class; + + // Attach the shared class and the validation rule to every checkbox in the group. + $variables['attributes']->addClass($group_class); + $variables['attributes']->setAttribute('data-rule-require_from_group', '[1,"' . $selector . '"]'); + $variables['attributes']->setAttribute('data-rule-required', 'false'); + } } /**