Form validation pattern
patterns specs/patterns/form-validation.kmd
How a form tells the user what is wrong and how to fix it: inline validation on blur, a top-of-form error summary that links to each offending field with a *corrective* message (not just "invalid"), required/optional marking on the label, two-tier helper text, progress on long forms, and an explicit success confirmation. Humanizes messages via the user-facing errors spec. Modeled after the gov.br Padrão Digital and GOV.UK error-summary. Distinct from form-page (which owns the save-bar and sectioning) — this pattern owns the *validation contract*.
Quando este padrão se aplica
Triggers primários
- Design the validation and error-recovery behavior of a form
Todos os triggers
- Validate form fields and show what the user must fix
- Add an error summary at the top of a form
- Mark required vs optional fields
- Show a success confirmation after a form submits
Corpo da especificação
Pattern — Form validation
Status: v0.1.0 — Draft. Promoted from the KDS × gov.br Design System gap analysis (
tools/design-gen/docs/govbr-ds-gap-analysis.md, GVB-14). Source: gov.br Padrão Digital de Governo (validation + error-summary with corrective action) and GOV.UK Design System error-summary.
R1 — When to use (vs form-page / wizard)
- Form validation (this): the contract for how any form surfaces errors and success — the field-level and summary-level behavior. It composes onto the other form patterns.
- Form page (
specs/patterns/form-page.kmd): the section-grouped edit surface with the sticky save bar. It hosts this validation contract at Save. - Wizard (
specs/patterns/wizard-multistep.kmd): per-step gates use this same contract at each step boundary.
R2 — Required / optional marking
- Mark fields at the label, never only by color or a lone asterisk. The default is to mark the smaller set: if most fields are required, mark the optional ones ("(optional)"); otherwise mark the required ones. State the convention once at the top of the form.
- The required/optional state is part of the accessible name (announced by screen readers), not a purely visual glyph.
R3 — Two-tier helper text
- Placeholder is a format hint only (e.g.
DD/MM/YYYY) and must never carry the label or a persistent instruction — it vanishes on input. - Persistent helper text (below the field) carries anything the user
must still see while typing: constraints, examples, why the field is
asked. Helper text is associated to the field (
aria-describedby).
R4 — Inline validation on blur (not on keystroke)
- Validate a field when the user leaves it (blur) or on submit — never on every keystroke (which nags mid-entry). Exception: a live constraint the user is actively working toward (password strength, character budget) may update live because it is progress feedback, not a rejection.
- Once a field has been marked invalid, re-validate it on input so the error clears as soon as it is fixed (don't make the user blur again to see success).
R5 — Corrective messages (the core rule)
- Every error states what is wrong and how to fix it, not just that the value is invalid. "Enter your date of birth" / "Date of birth must be a real date, like 31/03/1980" — never "Invalid field" or "Error".
- Messages are humanized per
specs/errors/user-facing-messages.kmd(plain language, no codes/stack terms, no blame). The message names the field so it reads standalone in the summary (R6). - Inline error text sits next to its field, is announced
(
aria-describedby+aria-invalid), and never relies on color alone (icon + text).
R6 — Error summary at the top (corrective + linked)
- On a failed submit, render an error summary at the top of the form: a titled block ("There is a problem") listing every error in field order, each a link that moves focus to the offending field.
- Each summary entry is the same corrective message as the inline error (R5) — the summary is scannable and actionable, not a generic count.
- Focus moves to the summary on failed submit; the summary is a labeled region so assistive tech announces it. The summary is empty/absent when the form is valid.
R7 — Progress on long forms
- A form long enough to scroll past one viewport (or split across steps) shows progress: a step indicator for wizards, or a section/position cue for one-page forms, so the user knows how much remains.
- Progress is advisory, not a gate — it never blocks the user from jumping to a known section (pairs with wizard-multistep's step nav).
R8 — Success confirmation
- A successful submit confirms explicitly — a confirmation page
(
specs/patterns/confirmation-page.kmd), an inline success banner, or a toast for low-stakes saves. Silence after submit is a defect: the user must know it worked and what happens next. - Success clears all error state and the unsaved-changes guard
(
specs/navigation/back-behavior.kmd).
R9 — Accessibility & i18n
- Errors set
aria-invalidand are described by their message; the summary is a focusable labeled region; required/optional is in the accessible name (R2). - Nothing is communicated by color alone — icon + text on every error and success state.
- All chrome (summary title, corrective messages, required/optional
labels, helper text, success copy) is translatable per
specs/i18n/contract.kmd. Corrective messages must survive translation (no concatenated fragments).
Não-escopo
- The save-bar / dirty tracking / sectioning (
specs/patterns/form-page.kmd). - Individual field components (text field, date input, selects).
- Server-side validation rules and schema (this is the presentation contract; the server is the source of truth for what is valid).
- Auth-specific error handling (login resolution has its own spec).
Referências
specs/patterns/form-page.kmdspecs/patterns/wizard-multistep.kmdspecs/errors/user-facing-messages.kmdspecs/navigation/back-behavior.kmdspecs/i18n/contract.kmd