Form summary
The <velin-form-summary> Web Component turns native constraint validation into an accessible error summary: one focusable panel listing every invalid field, with aria-invalid and aria-describedby wired up per field.
Why not native validation?
The browser's own validation bubble shows one error at a time, disappears on its own, cannot be styled, and is inconsistently exposed to assistive technology. On a form with several problems the user has to discover them one submit at a time.
This component sets novalidate on the form and takes over: on submit it collects every invalid field, renders a persistent summary, and moves focus there. Constraint validation itself is unchanged — required, type, pattern, min, maxlength and custom messages all still apply.
Basic
Place the element inside the form, or anywhere on the page with for pointing at the form id.
<form id="signup">
<velin-form-summary heading="There is a problem with your details"></velin-form-summary>
<label class="velin-form-label" for="email">Email address</label>
<input class="velin-form-control" id="email" name="email" type="email" required>
<button type="submit" class="velin-btn velin-btn--primary">Create account</button>
</form>
Submitting with errors renders a role="alert" panel, focuses it, and announces the number of fields needing attention. Each summary entry links to its field; activating one moves focus straight to the input. Errors clear per field as soon as that field becomes valid, so the summary shrinks as the user works through it — it never re-flags a field the user has not submitted yet.
Custom messages and containers
By default the browser's validationMessage is used, which is already localised. Override it per field, and control how the field is named in the summary:
<input id="name" name="name" required
data-error-message="Enter your full name"
data-error-label="Name">
Messages are injected as a .velin-field-error element after the field and referenced from aria-describedby. To control placement, supply your own container — the component fills it instead of creating one, and leaves it in the DOM when the error clears:
<input id="email" name="email" type="email" required>
<p class="velin-field-error" data-velin-error-for="email"></p>
Existing aria-describedby hints are preserved: the error id is appended, so a field can point at both its hint and its error.
Attributes
| Attribute | Default | Notes |
|---|---|---|
for | — | Form id; omit when nested inside the form |
heading | There is a problem | Summary heading text |
native-validation | — | Keeps the browser's own bubbles (does not set novalidate) |
Per-field attributes: data-error-message, data-error-label, data-error-ignore (skip the field entirely), and data-velin-error-for on a message container. Disabled fields are always skipped, and a radio group is listed once rather than per radio.
JavaScript API
const summary = document.querySelector('velin-form-summary');
summary.validate(); // returns true when the form is valid
summary.focusFirstError(); // move focus to the first invalid field
summary.clear(); // drop the summary and all field error state
summary.errors; // [{ field, label, message }]
summary.form; // the bound form element
Events
velin-form-invalid— submit blocked;detail.errorslists label and message per fieldvelin-form-valid— the last outstanding error clearedvelin-form-error-focus— a summary link moved focus;detail.nameidentifies the field
Accessibility
- Errors are identified in text next to each field and repeated in the summary (3.3.1 Error Identification).
- Messages describe how to fix the problem, from the browser or
data-error-message(3.3.3 Error Suggestion). aria-invalidandaria-describedbyare maintained automatically (4.1.2 Name, Role, Value).- The panel is
role="alert"and receives focus, so both screen reader and sighted keyboard users land on the problem (3.3.1, 2.4.3 Focus Order). - Summary entries are links to real field ids, and focus moves to the field on activation.
- The error count is announced assertively through
<velin-announcer>(4.1.3 Status Messages). - Errors are only shown after an explicit submit, never while the user is still typing a field for the first time.
Contract status in core/a11y/component-contracts.json: pass. Pair it with the CSS-only states on Validation and <velin-persist> for Redundant Entry.