<validated-form>
A Web Component that brings automatic browser form validation to your markup, displaying accessible error messages with zero configuration or frameworks.
Demo
This demo showcases the capabilities of the <validated-form> component, demonstrating various form fields and validation scenarios. Try submitting the form with different inputs to see how it handles validation and error messages.
Options
Use these controls to change how the <validated-form> demo behaves.
Code sample
HTML
The HTML markup for the demo form includes a variety of form controls, each with different validation requirements.
The form is wrapped in the <validated-form> component, which handles the validation logic and
error message display. Each form control uses aria-errormessage to reference an error element by its
id, where the validation message is displayed when the control is invalid. Descriptive text is associated
separately using aria-describedby to guide users on the expected input and validation rules.
<validated-form>
<form id="demo-form" autocomplete="off">
<p>Fields marked with * are required.</p>
<!-- Text / Email -->
<div>
<label for="text-input">Text input (email type)</label>
<p id="text-input-hint">Requires a valid email format.</p>
<input
type="email"
id="text-input"
name="text-input"
required
aria-describedby="text-input-hint"
aria-errormessage="text-input-error"
>
<div id="text-input-error" class="error" hidden></div>
</div>
<!-- Pattern -->
<div>
<label for="pattern-input">Pattern-validated input</label>
<p id="pattern-input-hint">Minimum 8 characters including letter, number, and symbol.</p>
<input
type="text"
id="pattern-input"
name="pattern-input"
pattern="^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z\d]).{8,}$"
required
aria-describedby="pattern-input-hint"
aria-errormessage="pattern-input-error"
>
<div id="pattern-input-error" class="error" hidden></div>
</div>
<!-- Number -->
<div>
<label for="number-input">Number input</label>
<p id="number-input-hint">Enter an even number between 2 and 10.</p>
<input
type="number"
id="number-input"
name="number-input"
required
min="2"
max="10"
step="2"
aria-describedby="number-input-hint"
aria-errormessage="number-input-error"
>
<div id="number-input-error" class="error" hidden></div>
</div>
<!-- Select -->
<div>
<label for="select-single">Single select</label>
<p id="select-single-hint">Choose one value.</p>
<select
id="select-single"
name="select-single"
required
aria-describedby="select-single-hint"
aria-errormessage="select-single-error"
>
<option value="">Select an option</option>
<option value="option-a">Option A</option>
<option value="option-b">Option B</option>
<option value="option-c">Option C</option>
</select>
<div id="select-single-error" class="error" hidden></div>
</div>
<!-- Multi Select -->
<div>
<label for="select-multiple">Multiple select</label>
<p id="select-multiple-hint">Select at least one value.</p>
<select
id="select-multiple"
name="select-multiple"
multiple
required
aria-describedby="select-multiple-hint"
aria-errormessage="select-multiple-error"
>
<option value="option-a">Option A</option>
<option value="option-b">Option B</option>
<option value="option-c">Option C</option>
<option value="option-d">Option D</option>
</select>
<div id="select-multiple-error" class="error" hidden></div>
</div>
<!-- Radio -->
<fieldset aria-describedby="radio-hint">
<legend>Single choice group (radio buttons)</legend>
<p id="radio-hint">Exactly one option must be selected.</p>
<label class="inline-option">
<input
type="radio"
name="radio-group"
value="option-a"
required
aria-errormessage="radio-group-error"
>
Option A
</label>
<label class="inline-option">
<input
type="radio"
name="radio-group"
value="option-b"
aria-errormessage="radio-group-error"
>
Option B
</label>
<label class="inline-option">
<input
type="radio"
name="radio-group"
value="option-c"
aria-errormessage="radio-group-error"
>
Option C
</label>
<div id="radio-group-error" class="error" hidden></div>
</fieldset>
<!-- Checkbox -->
<fieldset>
<legend>Checkbox group</legend>
<p id="checkbox-hint">Option A is required to proceed</p>
<label>
<input
type="checkbox"
name="checkbox-input-required"
value="option-a"
required
aria-describedby="checkbox-hint"
aria-errormessage="checkbox-input-required-error"
>
Option A
</label>
<div id="checkbox-input-required-error" class="error" hidden></div>
<label>
<input
type="checkbox"
name="checkbox-input-optional"
value="option-b"
>
Option B (Optional)
</label>
</fieldset>
<!-- File -->
<div>
<label for="file-input">File upload</label>
<p id="file-input-hint">Accepted formats: PDF, JPG, PNG.</p>
<input
type="file"
id="file-input"
name="file-input"
required
accept=".pdf,.jpg,.jpeg,.png"
aria-describedby="file-input-hint"
aria-errormessage="file-input-error"
>
<div id="file-input-error" class="error" hidden></div>
</div>
<!-- Textarea -->
<div>
<label for="textarea-input">Textarea</label>
<p id="textarea-hint">Not required, but must be between 20 and 300 characters if filled.</p>
<textarea
id="textarea-input"
name="textarea-input"
rows="4"
minlength="20"
maxlength="300"
aria-describedby="textarea-hint"
aria-errormessage="textarea-input-error"
></textarea>
<div id="textarea-input-error" class="error" hidden></div>
</div>
<!-- Custom validation errors -->
<div>
<label for="custom-errors-input">Input with custom error messages</label>
<p id="custom-errors-input-hint">Password field with custom validation messages for required and too-short errors.</p>
<input
type="password"
id="custom-errors-input"
name="custom-errors-input"
required
minlength="8"
autocomplete="off"
aria-describedby="custom-errors-input-hint"
aria-errormessage="custom-errors-input-error"
data-msg-required="Password is required."
data-msg-too-short="Password must be at least 8 characters."
>
<div id="custom-errors-input-error" class="error" hidden></div>
</div>
<!-- Disabled -->
<div>
<label for="disabled-input">Disabled control</label>
<p id="disabled-input-hint">This field is disabled. Although it is required, it will not trigger validation errors since it's not interactive.</p>
<input
type="text"
id="disabled-input"
name="disabled-input"
disabled
required
aria-describedby="disabled-input-hint"
aria-errormessage="disabled-input-error"
>
<div id="disabled-input-error" class="error" hidden></div>
</div>
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</form>
</validated-form>
CSS
The component does not include any default styles for error messages, allowing you to style them as needed.
The following CSS snippet provides a basic example using the error class applied to the error elements.
The styling of the form is omitted for brevity, but you can apply your own styles to the form controls and layout as desired.
.error {
color: light-dark(#ae1a28, #ff7d89);
font-size: 0.875rem;
word-wrap: break-word;
}
JavaScript
The JavaScript code listens for the form's submit event, prevents the default submission behavior,
and checks if the form is valid using the isValid() method provided by the <validated-form> component.
If the form is valid, it collects the form data into a FormData object and logs it to the console.
You can replace the console log with any custom submission logic, such as sending the data to a server or displaying a success message.
const validatedForm = document.querySelector('validated-form');
const form = document.querySelector('form');
form.addEventListener('submit', evt => {
evt.preventDefault();
if (!validatedForm.isValid?.()) {
return;
}
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
console.log('Form data:', data);
});
form.addEventListener('reset', evt => {
validatedForm.resetValidation?.();
});
License
Licensed under the The MIT License (MIT)