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 `search-bar.php` is purely decorative (the `<input>` carries all meaning). Add
`aria-hidden="true"` and `focusable="false"` to the SVG. `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. 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 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 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 because they label a group of checkboxes. These should use `<fieldset>/<legend>` instead
so the group label is programmatically associated with all its checkboxes. 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 "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 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"`) 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) #### 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 - [ ] **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, 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 #### 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 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"`. 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, 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. `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"> <label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_lecteurs_ext[0]" value="1"> Externe <input type="checkbox" name="jury_lecteurs_ext[0]" value="1"> Externe
</label> </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>
</div> </div>
<button type="button" class="admin-btn-secondary" style="margin-top:.5rem;" <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">' + '<label class="admin-checkbox-label admin-jury-ext">'
+ '<input type="checkbox" name="jury_lecteurs_ext[' + juryIdx + ']" value="1"> Externe' + '<input type="checkbox" name="jury_lecteurs_ext[' + juryIdx + ']" value="1"> Externe'
+ '</label>' + '</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); list.appendChild(div);
juryIdx++; juryIdx++;
} }

View File

@@ -179,7 +179,7 @@ try {
<label class="admin-checkbox-label admin-jury-ext"> <label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_lecteurs_ext[0]" value="1"> Externe <input type="checkbox" name="jury_lecteurs_ext[0]" value="1"> Externe
</label> </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>
<?php else: ?> <?php else: ?>
<?php foreach ($juryLecteurs as $li => $lm): ?> <?php foreach ($juryLecteurs as $li => $lm): ?>
@@ -190,7 +190,7 @@ try {
<input type="checkbox" name="jury_lecteurs_ext[<?= $li ?>]" value="1" <input type="checkbox" name="jury_lecteurs_ext[<?= $li ?>]" value="1"
<?= $lm['is_external'] ? 'checked' : '' ?>> Externe <?= $lm['is_external'] ? 'checked' : '' ?>> Externe
</label> </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>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?php endif; ?>
@@ -210,7 +210,7 @@ try {
+ '<label class="admin-checkbox-label admin-jury-ext">' + '<label class="admin-checkbox-label admin-jury-ext">'
+ '<input type="checkbox" name="jury_lecteurs_ext[' + juryIdx + ']" value="1"> Externe' + '<input type="checkbox" name="jury_lecteurs_ext[' + juryIdx + ']" value="1"> Externe'
+ '</label>' + '</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); list.appendChild(div);
juryIdx++; juryIdx++;
} }

View File

@@ -132,7 +132,7 @@ document.addEventListener('DOMContentLoaded', () => {
</select> </select>
<button type="submit" class="admin-filters-btn">Filtrer</button> <button type="submit" class="admin-filters-btn">Filtrer</button>
<?php if ($searchQuery || $yearFilter || $orientationFilter): ?> <?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; ?> <?php endif; ?>
</form> </form>
@@ -187,13 +187,18 @@ document.addEventListener('DOMContentLoaded', () => {
<td><?= htmlspecialchars($thesis['ap_program'] ?? 'N/A') ?></td> <td><?= htmlspecialchars($thesis['ap_program'] ?? 'N/A') ?></td>
<td> <td>
<?php if ($thesis['is_published']): ?> <?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: ?> <?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 endif; ?>
<?php if (!empty($thesis['access_type'])): ?> <?php if (!empty($thesis['access_type'])): ?>
<br><span class="status-badge status-access status-access--<?= strtolower(preg_replace('/[^a-z]/i', '', $thesis['access_type'])) ?>"> <?php
<?= htmlspecialchars($thesis['access_type']) ?> $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> </span>
<?php endif; ?> <?php endif; ?>
</td> </td>