Enhance/accessibility - Increase lighthouse accessibility score to 99%#239
Enhance/accessibility - Increase lighthouse accessibility score to 99%#239Samuelfaure wants to merge 29 commits into
Conversation
|
@Samuelfaure y'a déjà plusieurs PRs d'ouvertes sur l'a11y, tu peux checker si ça se recoupe, et si oui, review/merge et/ou intégrer dans ta PR en mentionnant les issues linear ? |
|
@skelz0r Les issues linear ont comme sujet de faire un audit, ici c'est pas vraiment un audit 🤔 Je vais recouper avec les autres PRs oui 👍 |
|
Isa a fait l'audit déjà :D |
254a23e to
f32052a
Compare
…ne (API-6739) - Pictos décoratifs (_show et _others) : alt="" au lieu de l'uid technique - <label> non associé à un contrôle → <p> (RGAA 8.9) - Lien DataPass target="_blank" : ajout rel="noopener noreferrer" et avertissement sr-only (RGAA 13.2) Ref: https://linear.app/pole-api/issue/API-6739
Le composant fr-sidemenu nécessite un <button class="fr-sidemenu__btn"> avec aria-controls/aria-expanded pour que le DSFR JS puisse afficher le menu de catégories sur mobile. Sans lui, la navigation par catégorie est inaccessible au clavier sur petits écrans. Ref: https://linear.app/pole-api/issue/API-6738
…I-6734) Les logos des fournisseurs dans les cartes du catalogue n'avaient pas d'attribut alt : Rails génère alors un alt par défaut (nom de fichier) vocalisé par les lecteurs d'écran. Ces logos sont décoratifs — le nom du fournisseur est déjà dans le texte adjacent. alt="" explicite. Idem pour le picto FranceConnect dans la carte API Particulier. Ref: https://linear.app/pole-api/issue/API-6734
…I-6736)
RGAA 8.2 / 8.9 / 9.3 — <ul> et <p> imbriqués dans <p> :
- Section CGU (Entreprise et Particulier) : <ul> sorti du <p>
- Bloc deprecated (Entreprise et Particulier) : idem
- Bloc incoming : <p> imbriqué dans <p> → deux paragraphes séparés
RGAA 9.2 — nav sans aria-label :
- <nav role="navigation"> → <nav aria-label="Sommaire de la page">
(role="navigation" redondant sur <nav> supprimé, class="" vide supprimée)
RGAA 1.1 — alt non pertinent :
- Logo fournisseur : "Logo du fournisseur de données" → "Logo de {nom}"
Ref: https://linear.app/pole-api/issue/API-6736
Typographic single quotes (U+2018/U+2019) were inadvertently introduced in the ERB files instead of ASCII single quotes, making the compiled Ruby code unparseable by Prism in certain contexts.
…C 8.6, 9.2, 12.1) - admin.html.erb: corrige typo "Entreculier" → "API Entreprise / API Particulier" (NC 8.6) - admin/editor/provider: ajout lien d'évitement DSFR (NC 12.1) - admin: <div fr-container> → <main id="content"> (NC 9.2) - editor/provider: <div fr-container> → <main id="content" class="fr-container ..."> (NC 9.2) - shared/admin/_header.html.erb: correction même typo dans le titre service
…title (NC 8.5, 9.2, 12.1) - layouts AE et AP : support content_for(:title) pour des titres de page dynamiques (NC 8.5) - layouts AE et AP : ajout lien d'évitement DSFR href="#main-content" (NC 12.1) - _containerized_body AE et AP : <div> → <main id="main-content"> (NC 9.2)
… 1.1, 1.2, 7.5, 9.1) - _modal: image loading.gif alt="" (NC 1.1) - _loader: alt="" + role="status" + fr-sr-only "Chargement en cours" (NC 1.1, 7.5) - _impersonate: role="alert" sur la div d'avertissement (NC 7.5) - section_access AE/AP: pictogrammes SVG alt="" (redondants avec le texte) (NC 1.2) et h4 → h3 pour la colonne droite (NC 9.1) - section_developers AE: aria-live="polite" sur le badge Hyperping (NC 7.5) Partially solves API-6954, API-6955
- footers AE/AP: fr-sr-only "(nouvelle fenêtre)" sur le lien licence etalab - menus AE/AP: fr-sr-only sur le lien "Demander un accès" (DataPass) - header tools AE/AP: fr-sr-only sur le lien page de statut - section_howto AE: fr-sr-only sur les liens DataPass et annuaire-entreprises - section_partners AE/AP: suppression target="_blank" sur routes internes - section_demonstrateur AP: link_to bloc avec fr-sr-only - locales AE/AP: fr-sr-only injecté dans les liens simplifions.data.gouv.fr en YAML Partially solves API-6952
…externe (NC 1.2, 3.1, 6.1, 7.5, 8.5, 9.1, 9.3, 13.2) - status.html.erb: lien <a> vide → aria-label statut + fr-sr-only (NC 6.1, 13.2) cercle CSS-only → aria-hidden + texte sr-only (NC 3.1) - index AE/AP: content_for :title (NC 8.5) + aria-live sur div "Aucun résultat" (NC 7.5) - show AE/AP: content_for :title (NC 8.5) + <h3 fr-alert__title> → <p> (NC 9.1) - _details AE: divs format/paramètres → <ul><li> (NC 9.3) + fr-sr-only liens status/tests (NC 13.2) - _details AP: fr-sr-only sur liens externes + alt="" sur pictogramme FranceConnect (NC 1.2, 13.2) - _property.html.erb: auto_link avec fr-sr-only sur les liens auto-détectés (NC 13.2) - _use_cases.html.erb: fr-sr-only sur les liens "Voir en détail" (NC 13.2) Partially solves API-6952
…NC 1.2, 7.1, 8.5, 9.1, 13.2) - _scopes.html.erb: <span tabindex="0"> → <h3 class="fr-accordion__title"> (NC 7.1) - show: aria-hidden="true" sur l'icône flèche (NC 1.2) - ask_for_prolongation: aria-hidden icône + fr-sr-only sur lien DataPass (NC 1.2, 13.2) - prolong: aria-hidden icône + fr-sr-only sur liens DataPass et CTA (NC 1.2, 13.2) - renew: fr-sr-only "(nouvelle fenêtre)" sur le CTA (NC 13.2) - stats: content_for :title + h4 → h2, h5 → h3 (NC 8.5, 9.1) Partially solves API-6952
…tut, DataPass (NC 3.1, 5.3, 9.1, 13.2) - sessions/_new: h2 → h1 class="fr-h2" (NC 9.1) + fr-sr-only sur lien ProConnect (NC 13.2) - wizard/_header: h2 → h1 class="fr-h2" (NC 9.1) + fr-sr-only sur lien DataPass (NC 13.2) - wizard/contacts: h4 → h3 (hiérarchie correcte sous h1) (NC 9.1) - wizard/prolonged: h3.fr-alert__title → p.fr-alert__title (NC 9.1) - wizard/owner + authorization_requests/_header: fr-sr-only sur liens DataPass (NC 13.2) - authorization_requests/show: caption tableau + h2 contact_technique → h3 (NC 5.3, 9.1) - pages/current_status: aria-hidden sur cercle + fr-sr-only statut textuel (NC 3.1, 13.2) Partially solves API-6952
….3, 6.1, 8.5, 9.3, 13.2) Titres de page (NC 8.5): - home AE/AP, accessibility, mentions, errors, profile, changelogs: content_for :title Cas d'usages: - _others: div.use_cases → ul.fr-raw-list + li (NC 9.3) + aria-label contextuel sur les liens "En savoir plus" (NC 6.1) - _simplifions_card: fr-sr-only "(ouverture dans un nouvel onglet)" (NC 13.2) - _endpoints_table: <caption class="fr-sr-only"> (NC 5.3) - index AE/AP: content_for :title (NC 8.5) Partially solves API-6952
Partner logo alt describes link destination rather than the image itself. Decorative pictos alongside headings get empty alt. Closes https://linear.app/pole-api/issue/API-6954
Adds external_link_to helper that appends a fr-sr-only "(nouvelle fenêtre)" span and enforces target=_blank + rel=noopener noreferrer. Migrates all external links in shared layout, footer, header, newsletter and home sections. Also removes target=_blank from the internal partner link and fixes the taget=_blank typo in the footer. Closes https://linear.app/pole-api/issue/API-6952
… (RGAA 6.1) Two issues fixed: - _donnees_personnelles partials (both APIs) had `href` without `mailto:` prefix, making the links non-functional - accessibility.html.erb, mentions.html.erb and _donnees_personnelles partials use raw email addresses as link text; added `title` attributes so screen readers announce the purpose when reading link lists out of context Closes https://linear.app/pole-api/issue/API-6953
Two issues on api_entreprise home: - _section_access: right-column cards used <h4> creating an h2→h4 jump relative to a preceding sibling h2; changed to <h3> (same level as the info items in the left column). Same fix applied to api_particulier. - _section_howto: "Accès à mon compte" sub-block used <h2> at the same level as the section title; demoted to <h3>. DSFR visual classes (fr-h4 etc.) are unaffected — only semantic level changed. Closes https://linear.app/pole-api/issue/API-6955
… 8.7) - "newsletter" → "lettre d'information" (DSFR official term) in api_particulier newsletter_title and api_entreprise iframe title - "changelog" in newsletter section titles wrapped with <span lang="en"> - "Swagger" and "OpenAPI" in section_developers wrapped with <span lang="en">; swagger_description values now rendered with .html_safe to allow HTML - "back-office" in api_entreprise section_use_cases description wrapped with <span lang="en"> Closes https://linear.app/pole-api/issue/API-6957
Covers API Entreprise and API Particulier. Skip link targets real ids already present in the DOM (#contenu, #navigation-header-menu, #footer). Non-regression: landmarks asserted on home, cas usages, catalogue, FAQ and an authenticated account page for both APIs (65 examples). Closes https://linear.app/pole-api/issue/API-6743
Two-layer strategy: - Views call `content_for(:page_title)` for dynamic titles (endpoint name, cas_usage name, blog post title, authorization request intitulé) - Static pages declare `page_title:` i18n keys under their controller/action; empty string = use site name only - `full_page_title` helper: checks content_for first, falls back to i18n, raises in local/test env if neither is set — guarantees no silent gaps Added `AbstractBlogPost#title` to parse the first `# ` heading from markdown. Closes https://linear.app/pole-api/issue/API-6951
f4c5f67 to
5e8696a
Compare
…g_posts, redoc v2/v3 (NC 8.5) Adds fallback page_title keys so static_page_title does not raise in Rails.env.local? when content_for(:page_title) is a no-op (nil intitule, nil blog post title, or view that never sets the key). - authorization_requests#show — both AE and AP - blog_posts#show — both AE and AP - pages#redoc_v2 and pages#redoc_v3 — AP only
…el (NC 1.2, 13.2) AP _section_partners had kept the old decorative alt "Logo fournisseur de données - …" while AE was already updated to the link-purpose label "Voir les API de …" (RGAA 6.1). Three different sr-only strings were used for the same semantic action (link opens in a new window): "(ouvre une nouvelle fenêtre)", "(ouverture dans un nouvel onglet)", and "(nouvelle fenêtre)". Normalised to the canonical "(nouvelle fenêtre)" from external_link_to across all templates.
…sing container
external_link_to reassigned html_options to {} in the block-given branch
when href was not a Hash, silently discarding any options passed as the
third positional argument (aria-*, data-*, class). Changed to only
override html_options when href is actually a Hash (the intended block-form
calling convention).
Admin layout <main> had no CSS classes while editor and provider layouts
both use fr-container fr-mb-5w fr-mt-5w, causing admin pages to render
full-width without layout constraints.
|
J'ai fait un Pré-audit assistée par IA (20% du RGAA environ), il restera encore toute la partie audit "humaine" à faire mais c'est top cela va me permettre d'aller plus vite sur les pages. 👍🏻 |
Isalafont
left a comment
There was a problem hiding this comment.
Super travail !!
Cela couvre une bonne partie des problèmes détectés et cela va grandement me faciliter l'audit "humaine" page par page restante à faire 🚀
J'ai ajouté des petits commentaires et des suggestions. N'hésites pas si tu veux que l'on en discute car tu connais le site bien mieux que moi et tu saura mieux m'orienter vers la bonne solution à te conseiller.
Je ne sais plus si dans les tickets que tu as mergé, il y avait celui du plan du site inclus ?
Ce que je peux te suggérer, mais c'est à toi de me dire, vu que la PR est déjà volumineuse, on peut très bien se dire que pour les premiers commentaires suggéré, on créé des tickets dédiés pour n'avoir à traiter que ces points séparément dans une autre PR.
Si tu préfères cela, on pourra merger celle ci. A toi de me dire !
| page_title: Mon compte | ||
| authorization_requests: | ||
| index: | ||
| page_title: Mes demandes |
There was a problem hiding this comment.
Suggestion: je changerais le titre en Mes habilitations qui me semble plus pertinent.
Le H1 de cette page est Habilitation API Particulier et la différence entre les 2 titres pourraient perturber certains utilisateurs.
| index: | ||
| page_title: Mes demandes | ||
| show: | ||
| page_title: Mon habilitation |
There was a problem hiding this comment.
Il me semble que ce titre n'est jamais atteint.
Cela correspond bien à cette page ?
Sur API Particulier, une fois connecté, la page show d’une habilitation n’affiche jamais le titre attendu Mon habilitation. Le <title> reste par exemple sur Mairie de Bordeaux, alors que l’utilisateur consulte bien le détail d’une habilitation.
Titre de page dynamique ignoré
Cela vient probablement du fait que la vue définit un bloc content_for :title, mais que le helper full_page_title ne lit que :page_title. Le titre dynamique défini par la vue n’est donc jamais pris en compte.
Suggestion : faire lire :title par full_page_title, ou harmoniser les vues pour utiliser uniquement content_for :page_title.
Exemple robuste côté helper :
def full_page_title
page_title =
if content_for?(:page_title)
content_for(:page_title)
elsif content_for?(:title)
content_for(:title)
else
t('.page_title')
end
[page_title, current_app_name].compact.join(' | ')
endCela permettrait aux pages comme la show d’une habilitation d’exposer correctement leur titre dynamique
dans <title>.
| <% if properties['description'] %> | ||
| <div class="description fr-highlight--beige-gris-galet fr-text--sm fr-pl-2w"> | ||
| <%= simple_format(auto_link(properties['description'], target: '_blank'), {}, wrapper_tag: 'div') %> | ||
| <%= simple_format(auto_link(properties['description'], target: '_blank', rel: 'noopener noreferrer') { |url| "#{url}<span class=\"fr-sr-only\">(nouvelle fenêtre)</span>".html_safe }, {}, wrapper_tag: 'div') %> |
There was a problem hiding this comment.
Le bloc passé à auto_link semble destiné à ajouter l’indication accessible (nouvelle fenêtre) aux URLs détectées automatiquement :
auto_link(properties['description'], target: '_blank', rel: 'noopener noreferrer') do |url|
"#{url}<span class=\"fr-sr-only\">(nouvelle fenêtre)</span>".html_safe
endMais dans le rendu actuel, ces URLs ne semblent pas ouvrir de nouvelle fenêtre / nouvel onglet. Dans ce cas, l’indication (nouvelle fenêtre) ne doit pas être ajoutée.
Il faudrait donc clarifier l’intention :
- soit ces liens doivent s’ouvrir dans le même onglet : supprimer le bloc et éviter target: '_blank' ;
- soit ces liens doivent s’ouvrir dans un nouvel onglet : s’assurer que target="_blank" est bien rendu et que le wrapper auto_link transmet le bloc.
Aujourd’hui, le bloc est trompeur car il suggère un comportement “nouvelle fenêtre” qui ne correspond pas au rendu observé.
Si je ne me trompe pas ça doit correspondre en test a une url se trouvant par exemple dans cette page
http://particulier.api.localtest.me:3000/catalogue/mesri/statut_etudiant
et pour tester :
dans :
commons/endpoints/_swagger_shared/mesri.yml:123
Et cette description est rendue par :
site/app/views/shared/endpoints/_property.html.erb:26
- Ouvrir http://particulier.api.localtest.me:3000/catalogue/mesri/statut_etudiant
- Aller dans la section des données.
- Chercher la propriété Établissement d'études.
- Inspecter le lien https://annuaire-education.fr.
- Vérifier si le HTML généré contient ou non le contenu attendu dans le lien.
Je suis preneuse d'en discuter avec toi 👍🏻
|
@Isalafont La PR plan du site est ici #204 ça ne chevauchait pas sur celle ci donc j'ai pas touché Je vais faire les quick-wins dans ta review et si y'a de gros morceaux on corrigera après 👍 comme ça on ship |
Address review feedback from PR #239: containerized_body partials opened a second <main> nested inside the layout's <main id="contenu">, breaking the unique-landmark rule. Also add tabindex="-1" to the three skip-link targets (#contenu, nav menu, footer) so Firefox/Safari actually move focus on activation, matching Chrome's native behavior.

Avant


Après


Reste à faire: check manuel des rendus
closes API-6742, API-6743, API-6951, API-6952, API-6953, API-6954, API-6955, API-6956, API-6957, API-6958