Add field-level aria-errormessage, aria-invalid, and aria-describedby across the TFE form

WCAG 3.3.1 (Error Identification): failing fields now get
aria-errormessage pointing to the flash-error container and
aria-invalid="true". WCAG 3.3.3 (Error Suggestion): <small>
hint text on inputs, selects, and file fields is now linked via
aria-describedby (always, not just on error).

Changes:
- text-field.php, select-field.php, checkbox-list.php: accept
  $errorFieldName; add aria-errormessage/aria-invalid on match;
  add id to <small> and aria-describedby on the control
- fieldset-tfe-info.php: aria-invalid on synopsis textarea
- fichiers-fragment.php: aria-describedby on cover, note
  d'intention, TFE, annexes, and website inputs; aria-invalid
  on format checkboxes when error matches 'formats'
- form.php: id="flash-error" + tabindex="-1" on flash-error
  div; accept $errorFieldName from callers
- admin/add.php: set $errorFieldName, wire $withAutofocusFn
  (was identity default)
- admin/edit.php: set $errorFieldName
- partage/index.php: consume autofocus field, wire autofocus
  function, add App::flashAutofocus() in submit catch block

Also fixes WCAG standards issue: removed invalid 'required'
HTML attribute from <fieldset> elements in checkbox-list.php
and fichiers-fragment.php (only aria-required stays). Added
role="group" for explicit ARIA semantics.
This commit is contained in:
Pontoporeia
2026-06-11 10:22:06 +02:00
parent c0ba99e861
commit e17246c850
11 changed files with 111 additions and 35 deletions

View File

@@ -12,6 +12,7 @@
* string|null $hint — optional hint shown in <small> below the input
* string|null $id — override the id attribute (defaults to $name)
* array $attrs — extra HTML attributes as key=>value pairs (e.g. min/max for number)
* string|null $errorFieldName — when set and matches $name, adds aria-invalid + aria-errormessage
*
* The partial does NOT call htmlspecialchars on $value — the caller is responsible.
*/
@@ -22,6 +23,16 @@ $placeholder = $placeholder ?? '';
$hint = $hint ?? null;
$id = $id ?? $name;
$attrs = $attrs ?? [];
$errorFieldName = $errorFieldName ?? null;
// Build hint id for aria-describedby
$hintId = ($hint !== null) ? ($id . '-hint') : null;
// Build describedby string (hint + error)
$describedBy = [];
if ($hintId !== null) $describedBy[] = $hintId;
if ($errorFieldName === $name) $describedBy[] = 'flash-error';
$ariaDescribedBy = !empty($describedBy) ? ' aria-describedby="' . implode(' ', $describedBy) . '"' : '';
$attrStr = '';
foreach ($attrs as $k => $v) {
@@ -31,6 +42,8 @@ foreach ($attrs as $k => $v) {
$attrStr .= ' ' . htmlspecialchars($k) . '="' . htmlspecialchars((string)$v) . '"';
}
}
$ariaInvalid = ($errorFieldName === $name) ? ' aria-invalid="true" aria-errormessage="flash-error"' : '';
?>
<div>
<label for="<?= htmlspecialchars($id) ?>"><?= htmlspecialchars($label) ?><?= $required ? ' <span class="asterisk">*</span>' : '' ?></label>
@@ -42,8 +55,10 @@ foreach ($attrs as $k => $v) {
value="<?= $value ?>"
<?= $required ? 'required' : '' ?>
<?= $placeholder ? 'placeholder="' . htmlspecialchars($placeholder) . '"' : '' ?>
<?= $attrStr ?>>
<small><?= htmlspecialchars($hint) ?></small>
<?= $attrStr ?>
<?= $ariaInvalid ?>
<?= $ariaDescribedBy ?>>
<small id="<?= htmlspecialchars($hintId) ?>"><?= htmlspecialchars($hint) ?></small>
</div>
<?php else: ?>
<input type="<?= htmlspecialchars($type) ?>"
@@ -52,9 +67,11 @@ foreach ($attrs as $k => $v) {
value="<?= $value ?>"
<?= $required ? 'required' : '' ?>
<?= $placeholder ? 'placeholder="' . htmlspecialchars($placeholder) . '"' : '' ?>
<?= $attrStr ?>>
<?= $attrStr ?>
<?= $ariaInvalid ?>
<?= $ariaDescribedBy ?>>
<?php endif; ?>
</div>
<?php
// Reset consumed variables so includes in a loop don't bleed state.
unset($type, $required, $placeholder, $hint, $id, $attrs, $attrStr, $k, $v);
unset($type, $required, $placeholder, $hint, $id, $attrs, $attrStr, $k, $v, $hintId, $describedBy, $ariaDescribedBy, $ariaInvalid, $errorFieldName);