fix(a11y): status badges no longer colour-only; fix aria on ✕ buttons (WCAG 1.4.1, 2.5.3)

admin/index.php — status badges (WCAG 1.4.1 Use of Colour):
  - Published badge: prefix ● symbol (aria-hidden) + aria-label="Statut : Publié"
  - Pending badge:   prefix ◌ symbol (aria-hidden) + aria-label="Statut : En attente"
  - Access badges (Libre/Interne/Interdit): prefix ○/◑/● symbol per type (aria-hidden)
    + aria-label="Accès : [type]"; symbol chosen from a PHP map keyed on the slug
  Each badge now communicates its state through shape AND colour, not colour alone.

admin/index.php — ✕ Réinitialiser link (WCAG 2.5.3 / 1.1.1):
  - ✕ wrapped in <span aria-hidden="true"> so the decorative symbol is skipped by
    screen readers; accessible name remains "Réinitialiser"

admin/add.php + admin/edit.php — jury remove buttons (WCAG 2.5.3):
  - All four ✕ remove buttons (2 static template rows + 2 JS-generated innerHTML strings)
    given aria-label="Supprimer ce lecteur"; the bare ✕ Unicode character has no
    speech equivalent so the aria-label replaces rather than supplements the label
This commit is contained in:
Pontoporeia
2026-03-31 16:10:41 +02:00
parent 338782947c
commit 77cc3caa0a
4 changed files with 20 additions and 15 deletions

10
TODO.md
View File

@@ -939,7 +939,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style
`search-bar.php` is purely decorative (the `<input>` carries all meaning). Add
`aria-hidden="true"` and `focusable="false"` to the SVG.
- [ ] **Admin `<nav>` logo is a text link - fine. But "✕ Réinitialiser" and "✕" remove buttons**
- [x] **Admin `<nav>` logo is a text link - fine. But "✕ Réinitialiser" and "✕" remove buttons**
use a bare Unicode `✕` as their visible label with no accessible name alternative.
For the "✕" jury-remove buttons in `add.php`/`edit.php`, add `aria-label="Supprimer ce membre du jury"`.
For "✕ Réinitialiser" in `index.php`, the text is adequate; the `✕` symbol is decorative
@@ -963,7 +963,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style
because they label a group of checkboxes. These should use `<fieldset>/<legend>` instead
so the group label is programmatically associated with all its checkboxes.
- [ ] **Status badges in `admin/index.php` convey state by colour alone** - "Publié" (green) /
- [x] **Status badges in `admin/index.php` convey state by colour alone** - "Publié" (green) /
"En attente" (yellow) / "Libre" (green) / "Interne" (blue) / "Interdit" (red) all rely
entirely on colour to distinguish states. This fails **1.4.1 Use of Colour**. Add a
visible non-colour distinction (e.g. a prefix icon character with `aria-hidden="true"`)
@@ -981,7 +981,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style
#### 1.4.1 Use of colour (see also 1.3.1 above)
- [ ] **Admin status badges distinguish states by colour only** - covered above.
- [x] **Admin status badges distinguish states by colour only** - covered above.
- [ ] **Active nav link has no non-colour indicator** - `.site-nav__link--active` is applied in
PHP but has no CSS rule at all (flagged in the semantic/CSS audit). Even if a rule existed,
@@ -1165,11 +1165,11 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style
#### 2.5.3 Label in name
- [ ] **`<a class="clear-filter">✕ Réinitialiser</a>`** - the visible label starts with a
- [x] **`<a class="clear-filter">✕ Réinitialiser</a>`** - the visible label starts with a
symbol. Fine as long as "Réinitialiser" is in the accessible name, which it is (it's text
content). No failure here, but the `` should be `aria-hidden="true"`.
- [ ] **Admin jury remove buttons ``** - the visible label is `` only. The accessible name
- [x] **Admin jury remove buttons ``** - the visible label is `` only. The accessible name
must contain (or start with) the visible label text. Since `` has no speech equivalent,
`aria-label="Supprimer ce lecteur"` replaces it entirely, which satisfies 2.5.3.

View File

@@ -115,7 +115,7 @@ function wasSelected($key, $value) {
<label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_lecteurs_ext[0]" value="1"> Externe
</label>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)" aria-label="Supprimer ce lecteur">✕</button>
</div>
</div>
<button type="button" class="admin-btn-secondary" style="margin-top:.5rem;"
@@ -132,7 +132,7 @@ function wasSelected($key, $value) {
+ '<label class="admin-checkbox-label admin-jury-ext">'
+ '<input type="checkbox" name="jury_lecteurs_ext[' + juryIdx + ']" value="1"> Externe'
+ '</label>'
+ '<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>';
+ '<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)" aria-label="Supprimer ce lecteur">✕</button>';
list.appendChild(div);
juryIdx++;
}

View File

@@ -179,7 +179,7 @@ try {
<label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_lecteurs_ext[0]" value="1"> Externe
</label>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)" aria-label="Supprimer ce lecteur">✕</button>
</div>
<?php else: ?>
<?php foreach ($juryLecteurs as $li => $lm): ?>
@@ -190,7 +190,7 @@ try {
<input type="checkbox" name="jury_lecteurs_ext[<?= $li ?>]" value="1"
<?= $lm['is_external'] ? 'checked' : '' ?>> Externe
</label>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)" aria-label="Supprimer ce lecteur">✕</button>
</div>
<?php endforeach; ?>
<?php endif; ?>
@@ -210,7 +210,7 @@ try {
+ '<label class="admin-checkbox-label admin-jury-ext">'
+ '<input type="checkbox" name="jury_lecteurs_ext[' + juryIdx + ']" value="1"> Externe'
+ '</label>'
+ '<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>';
+ '<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)" aria-label="Supprimer ce lecteur">✕</button>';
list.appendChild(div);
juryIdx++;
}

View File

@@ -132,7 +132,7 @@ document.addEventListener('DOMContentLoaded', () => {
</select>
<button type="submit" class="admin-filters-btn">Filtrer</button>
<?php if ($searchQuery || $yearFilter || $orientationFilter): ?>
<a href="/admin/" class="admin-filters-reset">Réinitialiser</a>
<a href="/admin/" class="admin-filters-reset"><span aria-hidden="true">✕ </span>Réinitialiser</a>
<?php endif; ?>
</form>
@@ -187,13 +187,18 @@ document.addEventListener('DOMContentLoaded', () => {
<td><?= htmlspecialchars($thesis['ap_program'] ?? 'N/A') ?></td>
<td>
<?php if ($thesis['is_published']): ?>
<span class="status-badge status-published">Publié</span>
<span class="status-badge status-published" aria-label="Statut : Publié"><span aria-hidden="true">● </span>Publié</span>
<?php else: ?>
<span class="status-badge status-pending">En attente</span>
<span class="status-badge status-pending" aria-label="Statut : En attente"><span aria-hidden="true">◌ </span>En attente</span>
<?php endif; ?>
<?php if (!empty($thesis['access_type'])): ?>
<br><span class="status-badge status-access status-access--<?= strtolower(preg_replace('/[^a-z]/i', '', $thesis['access_type'])) ?>">
<?= htmlspecialchars($thesis['access_type']) ?>
<?php
$accessSlug = strtolower(preg_replace('/[^a-z]/i', '', $thesis['access_type']));
$accessSymbols = ['libre' => '○', 'interne' => '◑', 'interdit' => '●'];
$accessSymbol = $accessSymbols[$accessSlug] ?? '●';
?>
<br><span class="status-badge status-access status-access--<?= $accessSlug ?>" aria-label="Accès : <?= htmlspecialchars($thesis['access_type']) ?>">
<span aria-hidden="true"><?= $accessSymbol ?> </span><?= htmlspecialchars($thesis['access_type']) ?>
</span>
<?php endif; ?>
</td>