mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-07 03:29:19 +02:00
docs: accessibility audit — add WCAG 2.1 AA analysis to TODO.md
Measured contrast ratios, traced every interactive element, checked all four
WCAG principles across public and admin surfaces. Current state confirmed:
zero ARIA attributes, zero skip links, zero focus-visible styles, zero
prefers-reduced-motion guards in the live codebase.
Key failures by criterion:
1.1.1: TFE file images use raw filename as alt; search bar SVG not aria-hidden;
jury remove buttons have no accessible name (bare ✕)
1.3.1: TFE metadata is div/span soup with no programmatic label-value association;
search filter selects have no associated label; checkbox groups need fieldset/legend
1.4.1: Status badges distinguish state by colour only; active nav link has no
non-colour indicator and its CSS class has no rule at all
1.4.3 (measured failures):
- Nav links at opacity:0.92 on purple: 4.05:1 (fails AA 4.5:1)
- filter-info purple text on purple-light bg: 4.08:1 (fails AA)
- Placeholder #aaa on white: 2.32:1 (fails AA)
- Gradient cards: white text on L=65% HSL — every warm hue fails AA,
some as low as 1.46:1 (yellow). Only blue/indigo hues pass.
- admin-text-muted #888 on bg-alt #242424: 4.38:1 (fails AA)
- admin-purple on dark bg: 3.57:1 (fails for normal text size)
1.4.10: Répertoire 4-column grid has no mobile breakpoint
1.4.11: Search select border #ddd on white: 1.6:1; admin input border: 1.8:1
2.1.1: Disabled pagination links have pointer-events:none but remain keyboard-focusable
2.4.1: No skip-to-main link anywhere in the site
2.4.4: Pagination arrows (« ‹ › ») have no aria-label
2.4.6: tfe.php h1=author h2=title is inverted; index/search have no h1 at all
2.4.7: No :focus-visible defined anywhere; outline:none suppresses browser default
on search input with no replacement
3.1.1: 429 response has no lang attribute
3.3.1: Form errors not announced as live regions; no autofocus on invalid field
3.3.2: Search input has no label, only placeholder
4.1.1: pages-edit.php has <link> in <body>
4.1.2: <video> has no caption track; <embed> PDF has no fallback download link;
bulk action results not announced to AT
Motion: no prefers-reduced-motion guard on any transition or animation
Infrastructure gaps: no .sr-only class, no skip link, no :focus-visible,
four explicit outline:none suppressions with no replacement
This commit is contained in:
413
TODO.md
413
TODO.md
@@ -919,3 +919,416 @@ Once the above is applied, the following classes become deletable (element name
|
||||
| `.admin-account-status__row` | `div` inside `<dl>` |
|
||||
| `.admin-account-status__label` | `dt` |
|
||||
| `.admin-danger-zone__description` | `p` |
|
||||
|
||||
---
|
||||
|
||||
## Accessibility audit (2026-03-26)
|
||||
|
||||
WCAG 2.1 AA is the baseline. Issues are grouped by criterion number for traceability.
|
||||
Current state: **zero ARIA attributes, zero skip links, zero focus-visible styles, zero
|
||||
`prefers-reduced-motion` guards** anywhere in the live codebase.
|
||||
|
||||
---
|
||||
|
||||
### 1 — Perceivable
|
||||
|
||||
#### 1.1.1 Non-text content (alt text)
|
||||
|
||||
- [ ] **Home card images use the thesis title as `alt`** — `alt="<?= $item['title'] ?>"` is a
|
||||
reasonable fallback, but the title alone provides no context about what the image depicts.
|
||||
Prefer `"Couverture — [titre] par [auteurs]"` for cover images, or `""` (empty) for purely
|
||||
decorative banners where the caption below already carries all the text information.
|
||||
For gradient placeholder cards there is no `<img>` at all — correct, no alt needed on a
|
||||
CSS gradient div.
|
||||
|
||||
- [ ] **TFE page file images use the raw filename as `alt`** — `alt="<?= $file['file_name'] ?>"`.
|
||||
A filename like `a3f8bc12.jpg` is meaningless to a screen reader user. Use the thesis title
|
||||
or a stored description field. If the `description` column in `thesis_files` is populated,
|
||||
that should be the alt text; fall back to the thesis title.
|
||||
|
||||
- [ ] **Search bar SVG icon has no `aria-hidden`** — the magnifying glass SVG in
|
||||
`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**
|
||||
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
|
||||
there and should be wrapped in `<span aria-hidden="true">✕</span>`.
|
||||
|
||||
#### 1.3.1 Info and relationships
|
||||
|
||||
- [ ] **The metadata list on `tfe.php` is a `<div>/<span>` soup** — a screen reader traversing
|
||||
the page hears "Orientation : Arts Numériques" as a flat run of text with no
|
||||
structure. There is no programmatic association between label and value. Replacing with
|
||||
`<dl>/<dt>/<dd>` (already flagged in the semantic audit) directly fixes this criterion.
|
||||
|
||||
- [ ] **Search filter `<select>` elements have no associated `<label>`** — each select is
|
||||
preceded by `<span class="search-filter-label">Année</span>` but this span is not a
|
||||
`<label>` and has no `for` attribute. Screen readers cannot associate it with the control.
|
||||
Fix: replace `<span>` with `<label for="filter-year">` and add `id="filter-year"` to
|
||||
the select (or use the wrapping-label pattern).
|
||||
|
||||
- [ ] **Admin form rows: `<label class="admin-label" for="X">` is correct** — the `for` attribute
|
||||
is present on all single-input rows in `add.php` and `edit.php`. Good. However, the
|
||||
multi-input rows (languages, formats) use `<label class="admin-label">` *without* a `for`
|
||||
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) /
|
||||
"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"`)
|
||||
and `aria-label="Statut : Publié"` on the badge `<span>`.
|
||||
|
||||
- [ ] **`<target="_blank">` links give no warning** — `tfe.php` and `apropos.php` open external
|
||||
links in a new tab with no indication. Screen reader users and keyboard users are
|
||||
disoriented when their context silently shifts. Add a visually-hidden `<span class="sr-only">(ouvre dans un nouvel onglet)</span>` after the link text, or append the information to the
|
||||
`aria-label`.
|
||||
|
||||
#### 1.3.4 / 1.3.5 Orientation & Input purpose
|
||||
|
||||
- [ ] **No `autocomplete` attributes on personal data fields** — `add.php`/`edit.php` fields
|
||||
like `auteurice` (person name), `mail` (contact) lack `autocomplete` hints. Add
|
||||
`autocomplete="name"`, `autocomplete="email"` where applicable so password managers and
|
||||
autofill can assist (WCAG 1.3.5).
|
||||
|
||||
#### 1.4.1 Use of colour (see also 1.3.1 above)
|
||||
|
||||
- [ ] **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,
|
||||
if it only changes colour it would still fail this criterion for users with colour blindness.
|
||||
The active indicator must include a non-colour signal: underline, border, weight change,
|
||||
or `aria-current="page"` (which is announced by screen readers regardless of visual styling).
|
||||
|
||||
#### 1.4.3 Contrast (minimum) — confirmed failures from measurement
|
||||
|
||||
- [ ] **Nav links at `opacity: 0.92` on purple background: 4.05:1** — fails AA (4.5:1 required
|
||||
for normal text). At full opacity the white-on-purple ratio is 4.87:1 (just passes), but
|
||||
the `opacity: 0.92` applied to `.site-nav__link` drops it to 4.05:1. Fix: remove the
|
||||
opacity reduction, or increase purple darkness slightly.
|
||||
|
||||
- [ ] **`filter-info` purple text `#9557b5` on purple-light background `rgba(149,87,181,0.12)`
|
||||
over white: 4.08:1** — fails AA. The filter info banner ("Année : 2024" or "Découvrez les
|
||||
TFE de 2024") uses purple text on a light purple tint. Use `var(--purple-dark)` (#7b3fa0)
|
||||
instead for the text, which would reach ~5.7:1.
|
||||
|
||||
- [ ] **Placeholder text `#aaa` on white: 2.32:1** — fails AA (and fails for large text too).
|
||||
Placeholder text is explicitly included in WCAG 1.4.3. Change to `#767676` minimum
|
||||
(~4.54:1) or preferably `#6b6b6b`.
|
||||
|
||||
- [ ] **Gradient card placeholder: white text on L=65% HSL backgrounds — most hues fail** —
|
||||
measured across the full hue range at `hsl(H, 60%, 65%)` (the lighter end of the gradient):
|
||||
only hues 240–250° (blue-indigo) pass AA. Every warm hue (0–230°) and most cool hues fail,
|
||||
with ratios as low as 1.46:1 at yellow (hue=60°). Since hue is derived from `$item['id'] % 360`,
|
||||
any thesis ID will produce a random hue. Fix: either darken the gradient to `L=45%` on the
|
||||
lighter end (would raise almost all hues above 3:1 for large text), or drop the text overlay
|
||||
inside the gradient entirely (the card caption below already shows title/author).
|
||||
|
||||
- [ ] **`admin-text-muted` `#888` on `admin-bg-alt` `#242424`: 4.38:1** — fails AA for normal
|
||||
text. This combination appears in table cell muted text, form hints, and sub-labels across
|
||||
the admin. Darken to `#909090` (~4.5:1) or use `#959595`.
|
||||
|
||||
- [ ] **Admin purple `#9557b5` used as large-heading colour on dark `#1a1a1a`: 3.57:1** — passes
|
||||
AA for large text (≥18pt/24px bold ≥14pt) but fails for normal text. Audit every place
|
||||
`var(--admin-purple)` appears as text colour on the dark background; ensure it is only
|
||||
used at sizes where 3:1 suffices (large text), not on body copy or small labels.
|
||||
|
||||
#### 1.4.4 Resize text
|
||||
|
||||
- [ ] **`body` has no `font-size` baseline set** — relies on browser default (16px). Most
|
||||
`.card__info`, `.search-filter-label` etc. use `rem` values which scale correctly. However,
|
||||
a few admin elements use absolute `px` sizes (`font-size: 0.78rem` is fine as it's rem-relative,
|
||||
but `width: 14px; height: 14px` on checkboxes does not scale). Minor — ensure no text
|
||||
is set in `px`.
|
||||
|
||||
#### 1.4.10 Reflow (320px viewport)
|
||||
|
||||
- [ ] **Répertoire index 4-column grid has no mobile breakpoint** — `search.css` defines the
|
||||
4-column grid at `1fr 2fr 2fr 1.5fr` with no `@media` fallback. At 320px the columns
|
||||
become ~50px wide each — unusable. Add a breakpoint to stack columns vertically below
|
||||
~600px (or 768px).
|
||||
|
||||
- [ ] **TFE page two-column grid collapses at 900px** — responsive breakpoints exist for `tfe.css`
|
||||
at 900px and 600px. Good. Verify the PDF `<embed>` at 700px height also reflows — currently
|
||||
`height: 700px` is fixed and causes horizontal overflow on small screens. Change to
|
||||
`height: clamp(300px, 80vh, 700px)`.
|
||||
|
||||
#### 1.4.11 Non-text contrast
|
||||
|
||||
- [ ] **Search filter `<select>` border is `#ddd` on white — 1.6:1** — the `border: 1px solid var(--border-color)` where `--border-color: #ddd` gives a 1.6:1 contrast ratio for the UI component boundary. WCAG 1.4.11 requires 3:1. Change to `#949494` minimum or use `#767676`.
|
||||
|
||||
- [ ] **Admin form inputs: `border-bottom: 1px solid #333` on `#1a1a1a` background — 1.8:1** —
|
||||
the bottom-border-only inputs have `border-bottom: 1px solid var(--admin-border)` (#333) on
|
||||
`#1a1a1a` background: 1.8:1. Fails 1.4.11. Raise to `#555` minimum (~3.1:1).
|
||||
|
||||
#### 1.4.12 Text spacing
|
||||
|
||||
- [ ] **No text-spacing override test done** — verify that when users apply the WCAG 1.4.12
|
||||
bookmarklet (line-height 1.5×, letter-spacing 0.12em, word-spacing 0.16em, paragraph spacing
|
||||
2em) the card grid and TFE metadata list do not overflow their containers. The `overflow: hidden`
|
||||
on `.card__media` and the tight `aspect-ratio: 4/3` on card images can cause content clipping.
|
||||
|
||||
---
|
||||
|
||||
### 2 — Operable
|
||||
|
||||
#### 2.1.1 Keyboard
|
||||
|
||||
- [ ] **Disabled pagination links are keyboard-reachable** — `<a class="pagination-btn disabled">`
|
||||
uses `.disabled { pointer-events: none }` in CSS which does not remove keyboard focus.
|
||||
A keyboard user can still Tab to these links and press Enter (which follows the href to
|
||||
page 0 or page N+1 unnecessarily). Fix: add `tabindex="-1"` and `aria-disabled="true"`
|
||||
when `$page <= 1` or `$page >= $totalPages`. Same issue in `search.php` inline-styled
|
||||
pagination links.
|
||||
|
||||
- [ ] **Jury "✕" remove buttons (`admin/add.php`, `admin/edit.php`) are only reachable via Tab**
|
||||
— no issue per se, but they have no visible label (just `✕`). Confirmed already in 1.1.1;
|
||||
adding `aria-label` also fixes keyboard discoverability.
|
||||
|
||||
- [ ] **Bulk-action JS buttons in `admin/index.php` call `bulkAction()` via `onclick`** — these
|
||||
are `<button type="button">` elements so they are keyboard-accessible. Confirm Enter and
|
||||
Space both trigger the action. Fine — no structural issue.
|
||||
|
||||
#### 2.1.2 No keyboard trap
|
||||
|
||||
- [ ] **EasyMDE editor in `pages-edit.php`** — CodeMirror-based editors are known keyboard
|
||||
traps; Tab inside the editor inserts a tab character rather than moving focus out. EasyMDE
|
||||
provides an escape route (Escape key exits the editor). Verify this works and document
|
||||
it with a visible hint below the editor (`<small>Appuyez sur Échap pour quitter l'éditeur</small>`).
|
||||
|
||||
#### 2.4.1 Bypass blocks — skip link
|
||||
|
||||
- [ ] **No skip-to-main-content link exists on any page** — every page loads with focus on
|
||||
the browser chrome, then Tab cycles through the nav and search bar before reaching `<main>`.
|
||||
On the home page that means tabbing through 4 nav links before reaching 24 thesis cards.
|
||||
Add `<a href="#main-content" class="skip-link">Aller au contenu principal</a>` as the
|
||||
first element inside `<body>`, visually hidden by default, visible on focus.
|
||||
Add `id="main-content"` to `<main>`. Add `.skip-link` styles to `common.css`.
|
||||
|
||||
#### 2.4.2 Page titled
|
||||
|
||||
- [ ] **`index.php` `<title>` is just "Posterg"** — no description of the page content.
|
||||
Change to "Posterg — Mémoires de l'ERG" or similar. Each page title should be unique and
|
||||
descriptive first: "Répertoire – Posterg", "À Propos – Posterg" (already good), but
|
||||
`tfe.php` uses just the thesis title without author: add author — "[Titre] — [Auteur] – Posterg".
|
||||
|
||||
#### 2.4.3 Focus order
|
||||
|
||||
- [ ] **Search filter form on `search.php` appears above `<main>` in the DOM but is rendered
|
||||
between the search bar and results visually** — the filter `<form class="search-controls">`
|
||||
comes before `<main>` in source order when `$hasSearch` is true. This is fine for focus
|
||||
order (source order = visual order). No issue.
|
||||
|
||||
- [ ] **On `tfe.php` the back link `← Retour` is at the bottom of the left column in DOM order**
|
||||
— a keyboard user must tab through the entire metadata list and synopsis before reaching
|
||||
it. Consider moving it to the top of the column (above `<h1>`), or adding a second copy
|
||||
near the top, so keyboard users can quickly exit. This is a UX recommendation, not a hard
|
||||
WCAG failure, but it affects 2.4.3 and 2.4.7.
|
||||
|
||||
#### 2.4.4 Link purpose
|
||||
|
||||
- [ ] **Home page cards: the link text is `author – title`** — adequate. However, if two theses
|
||||
share the same title (possible), two identical link texts exist. Consider adding the year:
|
||||
`author – title (year)` in a visually-hidden `<span class="sr-only">` appended to the link.
|
||||
|
||||
- [ ] **Search results cards: same issue** — `<span class="result-card__authors">` + title +
|
||||
meta inside `<a>`. The combined text read by screen readers will be "Author · Title · Year · Orientation"
|
||||
which is actually quite good. No hard failure here.
|
||||
|
||||
- [ ] **Pagination links use Unicode arrows `«`, `‹`, `›`, `»` as their only text** — these are
|
||||
announced by screen readers as "double left-pointing angle quotation mark" or similar
|
||||
gibberish. Add `aria-label` to each: `aria-label="Première page"`, `aria-label="Page précédente"`,
|
||||
`aria-label="Page suivante"`, `aria-label="Dernière page"`.
|
||||
|
||||
#### 2.4.6 Headings and labels
|
||||
|
||||
- [ ] **`tfe.php` heading hierarchy is inverted** — author is `<h1>`, thesis title is `<h2>`.
|
||||
The work's title is the primary topic of the page and should be `<h1>`. The author name is
|
||||
a label/metadata, not a heading. This is flagged in the semantic audit but it is also
|
||||
directly a WCAG 2.4.6 failure (heading does not describe the topic of the page).
|
||||
|
||||
- [ ] **`search.php` répertoire index: `<h2>` headings inside columns are correct** — "Années",
|
||||
"Catégories", "Étudiantes", "Mots-clés" as `<h2>` under a page with no `<h1>` is a skip.
|
||||
Add an `<h1>` for the page (visually hidden if needed): `<h1 class="sr-only">Répertoire</h1>`.
|
||||
Same for `index.php` which has no heading at all.
|
||||
|
||||
#### 2.4.7 Focus visible
|
||||
|
||||
- [ ] **No `:focus-visible` style defined anywhere in the public CSS** — `common.css`,
|
||||
`main.css`, `search.css`, `tfe.css`, and `apropos.css` contain zero `:focus` or
|
||||
`:focus-visible` rules. `modern-normalize` does not add any either. The browser's default
|
||||
focus ring is the only indicator, and it is suppressed by `outline: none` on
|
||||
`.site-search__input` in `common.css`. This is a clear WCAG 2.4.7 failure.
|
||||
Define a consistent focus style for all interactive elements:
|
||||
```css
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--purple);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
in `common.css`. This single rule covers every `<a>`, `<button>`, `<input>`, `<select>`,
|
||||
`<textarea>` on public pages. For admin: same using `var(--admin-purple)`.
|
||||
|
||||
- [ ] **`outline: none` on `.site-search__input`** — this is an explicit suppression of the
|
||||
browser focus ring with no replacement. Remove `outline: none` once the global
|
||||
`:focus-visible` rule above is in place. Same for `outline: none` on `.admin-input`,
|
||||
`.admin-select`, `.admin-textarea`, and `.search-filter-select`.
|
||||
|
||||
#### 2.5.3 Label in name
|
||||
|
||||
- [ ] **`<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
|
||||
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.
|
||||
|
||||
#### 2.5.5 Target size (advisory in WCAG 2.1, required in WCAG 2.2)
|
||||
|
||||
- [ ] **Pagination buttons are `2rem` (32px) height** — below the 44×44px recommended target.
|
||||
Increase to `min-height: 2.75rem` (44px) and `min-width: 2.75rem`.
|
||||
|
||||
- [ ] **Admin `.admin-btn-sm` (~28px height)** — used for Voir/Éditer/Publier/Dépublier in the
|
||||
TFE table. Well below 44px. Since these are in a dense table, 44px may not be practical;
|
||||
increase to at minimum 32px and add padding.
|
||||
|
||||
- [ ] **Admin bulk action buttons and jury remove `✕` buttons (~28px)** — same issue.
|
||||
|
||||
---
|
||||
|
||||
### 3 — Understandable
|
||||
|
||||
#### 3.1.1 Language of page
|
||||
|
||||
- [ ] **All public pages have `<html lang="fr">`** — correct. ✓
|
||||
- [ ] **`search.php` 429 response emits `<html>` with no `lang` attribute** — fails 3.1.1.
|
||||
Fix: `echo '<!DOCTYPE html><html lang="fr">…'`.
|
||||
|
||||
#### 3.2.1 On focus / 3.2.2 On input
|
||||
|
||||
- [ ] **No unexpected context changes on focus or input detected** — standard links and forms,
|
||||
no `onchange` redirects. ✓
|
||||
|
||||
#### 3.3.1 Error identification
|
||||
|
||||
- [ ] **`add.php` / `formulaire.php` validation errors are shown as a single flash message at
|
||||
the top of the page after a full round-trip** — the error says e.g. "Le champ 'Synopsis'
|
||||
est requis" but focus is not moved to the `<div class="admin-alert--error">` nor to the
|
||||
offending field. A screen reader user who has already moved past the alert region will not
|
||||
hear the error. Fix: add `role="alert"` to the error div (so it is announced as a live
|
||||
region on injection), and add `autofocus` to the first invalid field when re-rendering the
|
||||
form with session error data.
|
||||
|
||||
- [ ] **Client-side validation (`required` attributes)** — native browser validation is present
|
||||
on some fields (`required` on title, synopsis, etc.). The browser's native error popups are
|
||||
accessible but vary across browsers. No issue here, though the error messages cannot be
|
||||
styled consistently.
|
||||
|
||||
#### 3.3.2 Labels or instructions
|
||||
|
||||
- [ ] **`search-bar.php` input has no `<label>` — only `placeholder="Recherche..."`** —
|
||||
Placeholders disappear on focus and are not a substitute for labels. WCAG 3.3.2 requires
|
||||
labels or instructions for all inputs. Add a visually-hidden `<label for="site-search-input" class="sr-only">Recherche</label>` and `id="site-search-input"` on the input. Or use `aria-label="Recherche"` on the input directly.
|
||||
|
||||
- [ ] **Admin jury "Lecteur·ices" label has no `for` attribute** — `<label class="admin-label">Lecteur·ices :</label>` references no control (because the control is a dynamic list). The label should be a `<legend>` inside the enclosing `<fieldset>`, or the lecteur rows should be wrapped in their own `<fieldset>/<legend>`.
|
||||
|
||||
---
|
||||
|
||||
### 4 — Robust
|
||||
|
||||
#### 4.1.1 Parsing
|
||||
|
||||
- [ ] **`pages-edit.php` has a `<link>` element inside `<body>`** — invalid HTML. Confirmed in
|
||||
the semantic audit (section XV). Browsers tolerate it but validators flag it and some AT
|
||||
may misinterpret the document structure.
|
||||
|
||||
#### 4.1.2 Name, role, value
|
||||
|
||||
- [ ] **Custom checkbox "Externe" for jury members has no group label** — the checkbox
|
||||
`<input type="checkbox" name="jury_promoteur_ext">` is labelled by the adjacent
|
||||
`<label class="admin-checkbox-label admin-jury-ext">Externe</label>` which does wrap it.
|
||||
Good. But the word "Externe" alone provides no context about *what* is external. A screen
|
||||
reader user hears "Externe, checkbox, not checked" with no reference to the jury member.
|
||||
Use `aria-label="[Nom du promoteur] est externe"` set dynamically via JS when the name
|
||||
field is filled, or add a static `aria-describedby` pointing to the adjacent name input.
|
||||
|
||||
- [ ] **`<video>` elements on `tfe.php` have no captions** — `<video controls>` with no `<track kind="captions">`. For publicly uploaded video content, captions are required under WCAG 1.2.2
|
||||
(Captions — Prerecorded). This is a content/upload-time concern rather than a template fix,
|
||||
but the template should at minimum include a `<track>` slot and the admin upload form
|
||||
should document the requirement.
|
||||
|
||||
- [ ] **`<embed>` for PDFs has no accessible alternative** — `<embed type="application/pdf">` is
|
||||
not accessible to screen readers or keyboard users who cannot operate PDF viewers in-browser.
|
||||
Add a fallback download link below every embed:
|
||||
`<a href="/media.php?path=…&download=1">Télécharger le PDF</a>`.
|
||||
|
||||
- [ ] **Admin `<select>` for visibility/access in `edit.php` uses truncated option text** —
|
||||
`mb_strimwidth($at['description'], 0, 60, '…')` truncates the access type description to
|
||||
60 chars with an ellipsis. The truncated text becomes the accessible name of the option.
|
||||
Use the full description in the option text (or a `title` attribute), and keep the truncated
|
||||
text only for visual display.
|
||||
|
||||
- [ ] **Bulk publish/unpublish JS does not announce result to screen readers** — after
|
||||
`bulkAction()` submits the form and the page reloads, the success/error message appears
|
||||
in a `<div class="admin-alert">` with no `role="status"` or `role="alert"`. A screen reader
|
||||
will not announce it unless focus moves to it. Add `role="alert"` to error messages and
|
||||
`role="status"` to success messages across all admin pages.
|
||||
|
||||
---
|
||||
|
||||
### 5 — Additional: motion & user preferences
|
||||
|
||||
- [ ] **`prefers-reduced-motion` is not respected** — `main.css` has `transition: transform 0.3s ease`
|
||||
on card hover images (scale animation). `common.css`, `search.css`, and `admin.css` all
|
||||
have `transition: opacity/color/background 0.15s` rules. None are guarded by
|
||||
`@media (prefers-reduced-motion: reduce)`. Add:
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
transition-duration: 0.01ms !important;
|
||||
animation-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
in `common.css`. The card `transform: scale(1.02)` on hover is the most noticeable motion
|
||||
and should also be gated.
|
||||
|
||||
- [ ] **`prefers-color-scheme` is not respected** — the site has a fixed white public theme and
|
||||
a fixed dark admin theme. Users who have set their OS to dark mode will receive the white
|
||||
public site regardless. Not a WCAG failure (SC does not require dark-mode support) but
|
||||
worth noting as a quality-of-life improvement.
|
||||
|
||||
---
|
||||
|
||||
### 6 — Missing global infrastructure
|
||||
|
||||
These are things that must be added once and apply everywhere:
|
||||
|
||||
- [ ] **Add `.sr-only` utility class to `common.css`** — needed for skip links, visually-hidden
|
||||
labels, and screen-reader-only context text referenced throughout this audit:
|
||||
```css
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0,0,0,0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Add skip-to-content link in all page templates** — as described in 2.4.1 above. This
|
||||
one change has the highest impact-per-line-of-code ratio of any item in this audit.
|
||||
|
||||
- [ ] **Add global `:focus-visible` rule in `common.css` and `admin.css`** — as described in
|
||||
2.4.7. Second highest impact item.
|
||||
|
||||
- [ ] **Remove all `outline: none` declarations that have no replacement focus style** —
|
||||
`common.css:125`, `admin.css:121`, `admin.css:323`, `search.css:241`.
|
||||
|
||||
Reference in New Issue
Block a user