Validation 1.2.2
Pair CSS validation states with <velin-form-summary> so errors are visible, linked, and announced — not color-only.
Styles under .velin-form--validated target native :valid / :invalid (and :user-invalid where supported).
Deutsch: Validierung
<velin-form-summary> are stable in VelinStyle 1.2.0.
Defaults are WCAG 2.2 AAA-oriented — using them does not certify your application.
initA11y() is an optional runtime bootstrap (live announcer + scroll padding), not a validator.
When to use
- Multi-field forms that need a focusable error list after submit — add
<velin-form-summary>. - Reveal success/error borders after submit with
.velin-form--validatedplus.velin-field-error/.velin-field-valid. - Server or async failures: set
aria-invalid="true"and pointaria-describedbyat a message id (summary wires this automatically).
When not
- Do not treat colored borders alone as error identification — assistive tech needs text and
aria-invalid. - Do not expect
initA11y()to validate fields; call it only to mount the announcer / focus-not-obscured padding. - Do not claim AAA certification because tokens are AAA-oriented — test your flows with real AT.
How it works
Validation styles are scoped under .velin-form--validated. Add this class to the
<form> (typically on submit) to reveal valid/invalid borders on
.velin-input, .velin-select, and .velin-textarea.
Pair with visible messages: .velin-field-valid and .velin-field-error.
For programmatic or server errors, use [aria-invalid="true"] (styled in the core bundle)
or modifiers .velin-input--error / .velin-input--success.
ARIA, :user-invalid, and honesty
The shipped CSS styles [aria-invalid="true"] and, under .velin-form--validated,
:user-invalid / :user-valid where the browser supports them (avoids “all red on load”).
Helpers include .velin-field-error, .velin-field-hint, and .velin-field-valid.
initA11y() does not set aria-invalid.
Import from @birdapi/velinstyle/a11y when you want a shared live region and scroll padding for fixed navs:
import { initA11y } from '@birdapi/velinstyle/a11y';
initA11y({ announcer: true, scrollPadding: true });
Prefer <velin-form-summary> to set aria-invalid and aria-describedby on submit.
For hand-rolled scripts, set both yourself when server-side or async validation fails.
Live example
<form class="velin-form--validated" novalidate>
<div class="velin-field">
<label class="velin-label" for="name">Name</label>
<input type="text" class="velin-input" id="name" value="Jane Doe" required>
<div class="velin-field-valid">Looks good!</div>
</div>
<div class="velin-field">
<label class="velin-label" for="email">Email</label>
<input type="email" class="velin-input" id="email" required>
<div class="velin-field-error">Please enter a valid email address.</div>
</div>
<button type="submit" class="velin-btn velin-btn--primary">Submit form</button>
</form>
JavaScript trigger
Add .velin-form--validated on submit to activate styles only after user interaction:
document.querySelectorAll('form[novalidate]').forEach(form => {
form.addEventListener('submit', event => {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('velin-form--validated');
});
});
Server-side validation
For server-rendered errors, apply .velin-input--success or .velin-input--error directly to
the control — no parent .velin-form--validated needed.
<input type="text" class="velin-input velin-input--success" value="velinuser">
<div class="velin-field-valid">Username is available.</div>
<input type="password" class="velin-input velin-input--error" aria-invalid="true">
<div class="velin-field-error">Password must be at least 8 characters.</div>
Error summary (<velin-form-summary>)
The CSS above styles one field at a time. On a longer form, screen reader and keyboard users also need a
single place that names every problem.
<velin-form-summary> takes over submit
handling, builds a focusable list of errors, wires aria-invalid and
aria-describedby per field, and announces the error count through
<velin-announcer>.
This covers the WCAG 2.2 criteria that plain CSS cannot:
3.3.1 Error Identification, 3.3.3 Error Suggestion, and the
aria-describedby half of 4.1.2 Name, Role, Value. Pair it with
<velin-persist> for
3.3.7 Redundant Entry.
Submit empty to see the summary, aria-invalid, and linked field messages.
<form id="signup">
<velin-form-summary heading="Please fix the following"></velin-form-summary>
<div class="velin-field">
<label class="velin-label" for="email">Email</label>
<input class="velin-input" type="email" id="email" name="email" required
data-error-message="Enter a valid email address">
</div>
<button class="velin-btn velin-btn--primary" type="submit">Sign up</button>
</form>
Per-field wording comes from data-error-message and data-error-label; without
them the element falls back to the browser’s validationMessage and the associated
<label>. Use data-error-ignore to skip a control and
native-validation to keep the browser bubbles. The element emits
velin-form-invalid, velin-form-valid, and
velin-form-error-focus; validate(), clear() and
focusFirstError() are available for custom flows. Full reference:
Form summary.
Accessibility
<velin-form-summary>setsaria-invalid, appends error ids toaria-describedby, and renders a focusablerole="alert"list (supports 3.3.1, 3.3.3, and name/role/value wiring).- Announce error counts via
<velin-announcer>—initA11y({ announcer: true })ensures a shared region exists; it does not invent messages. - Never rely on color alone; keep
.velin-field-errortext next to the control.
Related
- Form summary — full API, events,
data-error-message - Persist — redundant entry / draft values
- Accessibility —
initA11y, AAA-oriented defaults - Form control · Forms overview