
validation-enhancer is a JavaScript web component that upgrades native HTML5 form validation with the inline error messages, CSS state classes, and accessibility behavior that the browser’s built-in validation never provides on its own.
Wrap any <form> in the <validation-enhancer> custom element and you get per-field error text, .valid and .invalid class toggling, aria-invalid management, and a scroll-to-first-error behavior on submit.
A companion element, <validation-enhancer-zod>, replaces the HTML attribute checks with a Zod schema, so a single z.object() definition drives all field rules and error messages.
Features:
- Enhances native HTML form validation inside a custom element.
- Displays validation messages next to each form field.
- Shows errors after focus leaves a field or after form submit.
- Clears validation text after the user fixes a field.
- Focuses the first invalid field on form submit.
- Scrolls the first invalid field or its label into view.
- Supports custom validation text through HTML attributes.
- Supports Zod schemas for schema-first validation.
- Watches newly added forms and fields after page load.
- Adds field state classes for custom CSS styling.
How To Use It:
Installation
1. Install & import validation-enhancer.
npm install validation-enhancer
import "validation-enhancer";
2. Import the Zod version only when you need schema validation.
import "validation-enhancer-zod";
3. For a browser or demo use, copy the compiled file from the dist directory into your project and load it as a module.
<script type="module" src="validation-enhancer.min.js"></script>
4. Or use the Zod build for schema-based validation.
<script type="module" src="validation-enhancer-zod.min.js"></script>
5. Available builds:
dist/validation-enhancer.js dist/validation-enhancer.min.js dist/validation-enhancer.compat.min.js dist/validation-enhancer-zod.js dist/validation-enhancer-zod.min.js dist/validation-enhancer-zod.compat.min.js
The compat builds target ES2017 output. The Zod builds include the main validation enhancer component.
The required HTML markup
Wrap the form in <validation-enhancer>. Add an error message element for each field and connect it with aria-errormessage.
<validation-enhancer>
<form id="newsletter-form" action="/newsletter" method="post">
<label for="subscriber-email">Email address</label>
<input
id="subscriber-email"
name="email"
type="email"
required
aria-errormessage="subscriber-email-error"
>
<div id="subscriber-email-error" class="field-error"></div>
<button type="submit">Subscribe</button>
</form>
</validation-enhancer>
Basic Usage
The smallest useful setup needs the wrapper component, a form, at least one validatable input, a message target, a small amount of CSS, and the module script.
<validation-enhancer>
<form id="contact-form" action="/contact" method="post">
<label for="contact-email">Email address</label>
<input
id="contact-email"
name="email"
type="email"
required
aria-errormessage="contact-email-error"
validation-valueMissing="Enter your email address."
validation-typeMismatch="Enter a valid email address."
>
<div id="contact-email-error" class="field-error"></div>
<button type="submit">Send message</button>
</form>
</validation-enhancer>
<style>
input.valid {
border-color: #2e7d32;
}
input.invalid {
border-color: #c62828;
}
.field-error {
color: #c62828;
font-size: 0.875rem;
margin-top: 0.35rem;
}
</style>
<script type="module" src="validation-enhancer.min.js"></script>
The component disables the browser’s default validation popup for wrapped forms. It checks each input through the standard checkValidity() API. It writes the validation message into the linked error element, updates field classes, and sets aria-invalid="true" on invalid fields.
Configuration Options
valid-class(String): Sets the class added to fields that pass validation. Default value isvalid.invalid-class(String): Sets the class added to fields that fail validation. Default value isinvalid.message-target-attr(String): Set the attribute used to find each field’s message element. Default value isaria-errormessage.message-prefix(String): Sets the prefix for custom validation message attributes. Default value isvalidation.message-aria-live(String): Sets thearia-livevalue added to message elements. Default value ispolite.scroll-behavior(String): Sets thebehavioroption passed toscrollIntoView(). Default value issmooth.scroll-block(String): Sets theblockoption passed toscrollIntoView().scroll-inline(String): Sets theinlineoption passed toscrollIntoView().scroll-align-to-top(Boolean-like String): Uses the boolean form ofscrollIntoView()when present. Usefalseto passfalse; any other value passestrue.scroll-container(String): Sets a CSS selector for a custom scroll container. The component scrolls that container instead of the document.
Custom validation message attributes follow browser validity keys.
<input id="signup-email" name="email" type="email" required aria-errormessage="signup-email-error" validation-valueMissing="Email is required." validation-typeMismatch="Use a valid email address." > <div id="signup-email-error"></div>
The component also accepts the data- form.
<input required aria-errormessage="name-error" data-validation-valueMissing="Enter your name." > <div id="name-error"></div>
Supported validity keys:
validation-valueMissing(String): Message for an empty required field.validation-typeMismatch(String): Message for a value that does not match the input type.validation-patternMismatch(String): Message for a value that does not match the input pattern.validation-tooLong(String): Message for a value that exceedsmaxlength.validation-tooShort(String): Message for a value shorter thanminlength.validation-rangeUnderflow(String): Message for a number belowmin.validation-rangeOverflow(String): Message for a number abovemax.validation-stepMismatch(String): Message for a number that does not matchstep.validation-badInput(String): Message for a value the browser cannot parse.
API Methods
// Set a Zod schema on a validation-enhancer-zod element.
// Input names must match schema field names.
const enhancedForm = document.querySelector("validation-enhancer-zod");
enhancedForm.setZodSchema(schema);
// Read the current Zod schema from a validation-enhancer-zod element.
const currentSchema = enhancedForm.getZodSchema();
// Set a custom validity message on a native form field.
// This uses the standard HTMLFormElement API.
const emailInput = document.querySelector("#account-email");
emailInput.setCustomValidity("This email address is already in use.");
// Clear a custom validity message.
emailInput.setCustomValidity("");
// Trigger validation after a script changes a field value.
emailInput.value = "[email protected]";
emailInput.dispatchEvent(new Event("change", { bubbles: true }));
Events and Integration Hooks
validation-enhancer does not expose official custom events. It listens to standard DOM events inside the wrapped component.
const form = document.querySelector("#profile-form");
// Integration hook: run custom code before a form leaves the page.
form.addEventListener("submit", (event) => {
if (!form.checkValidity()) {
return;
}
console.log("The form passed native validation.");
});
// Integration hook: react when a field value changes.
form.addEventListener("change", (event) => {
if (event.target.matches("input, select, textarea")) {
console.log("Changed field:", event.target.name);
}
});
// Integration hook: trigger validation after programmatic value changes.
const username = document.querySelector("#profile-username");
username.value = "new-editor";
username.dispatchEvent(new Event("change", { bubbles: true }));
Advanced Example 1: Contact Form with Custom Messages
<validation-enhancer>
<form id="support-form" action="/support" method="post">
<label for="support-name">Name</label>
<input
id="support-name"
name="name"
required
minlength="2"
aria-errormessage="support-name-error"
validation-valueMissing="Enter your name."
validation-tooShort="Use at least 2 characters."
>
<div id="support-name-error" class="form-error"></div>
<label for="support-email">Email</label>
<input
id="support-email"
name="email"
type="email"
required
aria-errormessage="support-email-error"
validation-valueMissing="Enter your email address."
validation-typeMismatch="Use a valid email format."
>
<div id="support-email-error" class="form-error"></div>
<label for="support-message">Message</label>
<textarea
id="support-message"
name="message"
required
minlength="20"
aria-errormessage="support-message-error"
validation-valueMissing="Enter a support message."
validation-tooShort="Use at least 20 characters."
></textarea>
<div id="support-message-error" class="form-error"></div>
<button type="submit">Send request</button>
</form>
</validation-enhancer>
<script type="module" src="validation-enhancer.min.js"></script>
Advanced Example 2: Custom Class Names and Message Prefix
<validation-enhancer
valid-class="is-valid"
invalid-class="has-error"
message-prefix="form"
>
<form id="billing-form" action="/billing" method="post">
<label for="billing-postcode">ZIP code</label>
<input
id="billing-postcode"
name="postcode"
required
pattern="[0-9]{5}"
aria-errormessage="billing-postcode-error"
form-valueMissing="Enter a ZIP code."
form-patternMismatch="Use a 5 digit ZIP code."
>
<div id="billing-postcode-error" class="field-message"></div>
<button type="submit">Save billing address</button>
</form>
</validation-enhancer>
<style>
input.is-valid {
border-color: #2e7d32;
}
input.has-error {
border-color: #b00020;
}
.field-message {
color: #b00020;
}
</style>
<script type="module" src="validation-enhancer.min.js"></script>
Advanced Example 3: Zod Schema Validation
<validation-enhancer-zod>
<form id="account-form" action="/account" method="post">
<label for="account-email">Email</label>
<input
id="account-email"
name="email"
type="email"
aria-errormessage="account-email-error"
>
<div id="account-email-error" class="form-error"></div>
<label for="account-password">Password</label>
<input
id="account-password"
name="password"
type="password"
aria-errormessage="account-password-error"
>
<div id="account-password-error" class="form-error"></div>
<button type="submit">Create account</button>
</form>
</validation-enhancer-zod>
<script type="module">
import { z } from "https://esm.sh/zod";
import "validation-enhancer-zod";
const schema = z.object({
email: z.string()
.min(1, "Email is required.")
.email("Enter a valid email address."),
password: z.string()
.min(8, "Use at least 8 characters.")
});
customElements.whenDefined("validation-enhancer-zod").then(() => {
const formEnhancer = document.querySelector("validation-enhancer-zod");
formEnhancer.setZodSchema(schema);
});
</script>
Zod validation matches fields by name. A field with no name attribute does not map to a schema field.
Advanced Example 4: Dynamic Fields After Page Load
<validation-enhancer>
<form id="team-form" action="/team" method="post">
<div id="member-list">
<label for="member-1">Team member email</label>
<input
id="member-1"
name="members[]"
type="email"
required
aria-errormessage="member-1-error"
validation-valueMissing="Enter an email address."
validation-typeMismatch="Use a valid email address."
>
<div id="member-1-error" class="field-error"></div>
</div>
<button type="button" id="add-member">Add member</button>
<button type="submit">Invite team</button>
</form>
</validation-enhancer>
<script type="module" src="validation-enhancer.min.js"></script>
<script>
let memberCount = 1;
document.querySelector("#add-member").addEventListener("click", () => {
memberCount += 1;
const fieldId = `member-${memberCount}`;
const errorId = `${fieldId}-error`;
document.querySelector("#member-list").insertAdjacentHTML("beforeend", `
<label for="${fieldId}">Team member email</label>
<input
id="${fieldId}"
name="members[]"
type="email"
required
aria-errormessage="${errorId}"
validation-valueMissing="Enter an email address."
validation-typeMismatch="Use a valid email address."
>
<div id="${errorId}" class="field-error"></div>
`);
});
</script>
The component watches added elements inside the wrapper. New inputs and message targets join the validation flow after insertion.
Alternatives and Related Resources:
- 10 Best Form Validation JavaScript Plugins: Compare jQuery and JavaScript form validation libraries for client-side validation work.
- jQuery Validation: Use a mature jQuery validation plugin for rule-based validation in legacy jQuery projects.
- Parsley.js: Add declarative client-side validation to forms through HTML attributes.
- 10 Best Pure JavaScript Form Validation Libraries: Review vanilla JavaScript form validation libraries for projects that do not use jQuery.
- Fast React Forms with Zod Validation and shadcn/ui: Build React form UIs with shadcn/ui, React Hook Form, and Zod.







