ETQ instructeur, l'erreur de motivation manquante s'affiche au format DSFR#13455
Open
colinux wants to merge 7 commits into
Open
ETQ instructeur, l'erreur de motivation manquante s'affiche au format DSFR#13455colinux wants to merge 7 commits into
colinux wants to merge 7 commits into
Conversation
colinux
marked this pull request as draft
July 9, 2026 16:46
colinux
force-pushed
the
ux-decision-motivation-validation
branch
from
July 9, 2026 16:47
4f5b657 to
89fbb62
Compare
colinux
force-pushed
the
ux-decision-motivation-validation
branch
from
July 10, 2026 09:38
89fbb62 to
2f65c9e
Compare
Native constraint validation keeps blocking the submission of an empty mandatory motivation, but its browser bubble is replaced by the DSFR error pattern: forms opt in with data-dsfr-validation, the server renders the error text hidden next to the input, and the existing global form-validation module toggles the DSFR error classes from the "invalid" event (suppressing the native bubble and focusing the first invalid field). The error clears as soon as the input becomes valid again. No server round-trip is involved, so the modal state (justificatif direct upload included) is untouched when validation fails.
Client-side validation is the primary UX, but nothing enforced the mandatory motivation server-side: a crafted request could refuse or classify a dossier without any motivation. Guard both decision endpoints (single and batch) with a flash + redirect safety net.
colinux
force-pushed
the
ux-decision-motivation-validation
branch
from
July 13, 2026 16:01
38ea6aa to
98c4e3c
Compare
No behavior change: event targets are narrowed with the coldwired/utils type guards already used across controllers.
Use DSFR's fr-btn--align-on-content modifier to cancel the tertiary button's horizontal padding, so the optional justificatif action lines up with the motivation textarea's left edge. Replaces the ineffective fr-ml-0 previously used on the dropdown variant.
The dropdown motivation form was marked with the "inactive" class to
opt its <li> out of the .dropdown-items hover styling. That class also
matches the global .inactive { opacity: 0.5 } utility, so the fully
interactive form rendered at half opacity.
Key the hover exclusion on the already-present "form-inside" class
instead, and drop "inactive" from the menu items. The global utility is
left untouched for its legitimate use (quotient familial).
Clicking "Ajouter un justificatif" hid the button while it held focus. coldwired's hide() moves focus to the previous focusable element, so focus jumped to the motivation textarea. It also only revealed the file input's "Parcourir" button instead of opening the picker. Now the file input is clicked to open the native picker, and focus is sent to it through coldwired's focus action, applied before hide so the button no longer holds focus and hide does not steal it back to motivation.
colinux
marked this pull request as ready for review
July 16, 2026 10:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suite des retours UX du parcours d'instruction, pages 4 , 5, 8
Contexte
Dans la modale « Rendre une décision » (unitaire et lot), une motivation obligatoire manquante était signalée par la bulle native du navigateur, hors charte DSFR.
Ce que fait cette PR
data-dsfr-validation, le serveur rend le message d'erreur masqué à côté du champ, et le module globalform-validation.tsbascule les classes DSFR depuis l'événementinvalid(focus sur le champ en erreur, effacement dès que la saisie redevient valide). Mécanisme opt-in, réutilisable tel quel par d'autres formulaires.Pourquoi pas un traitement de formulaire classique ?
Le round-trip serveur avec re-rendu du formulaire en erreur a été prototypé puis écarté : re-rendre la modale détruit l'état client du direct upload du justificatif et oblige à restructurer le formulaire batch imbriqué — environ deux fois plus de code pour la même promesse UX. Ici, l'erreur est instantanée, aucune requête ne part tant que le formulaire est invalide, et l'état de la modale (justificatif compris) n'est jamais perturbé.
Retours UX complémentaires
Trois ajustements sur le même flux de décision, suite à relecture :
fr-btn--align-on-content, qui aligne le contenu du bouton sur le champ (en remplacement d'unfr-ml-0inopérant côté menu déroulant).<li>du menu portait la classeinactivepour neutraliser le survol des items du menu, mais cette classe déclenche aussi l'utilitaire globaleopacity: 0.5— le formulaire, pourtant pleinement interactif, s'affichait à moitié transparent. L'exclusion du survol s'appuie désormais surform-inside, ce qui supprime l'opacité parasite sans toucher à l'utilitaire globale.input[type=file](bouton « Parcourir »), et surtout le focus sautait sur le champ motivation —hide()de coldwired déplace le focus vers l'élément focalisable précédent quand on masque le bouton qui le porte. Le clic ouvre désormais directement le sélecteur natif, et le focus est envoyé sur l'input fichier via l'actionfocusde coldwired appliquée avant lehide(le bouton ne porte alors plus le focus, coldwired ne le vole plus vers motivation).