Skip to main content
VelinStyle v1.3.0
⌂ Home
  1. Docs
  2. Forms
  3. Validation

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

Maturity: Form CSS states and <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

When not

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.

Framework defaults are AAA-oriented support for criteria such as 3.3.1 and 3.3.3 when you use form-summary correctly — not a certification of your site.

Live example

Looks good!
Please enter a valid email address.
City is required.
You must agree before submitting.
<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.

Username is available.
Password must be at least 8 characters.
<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