Validates and sanitizes the inputs in a form using native HTML attributes.
npm install @jdlien/validatorValidator is a lightweight utility class that adds automatic validation to your HTML forms that works much like
the HTML5 form validation provided by browsers, but it is much more powerful, flexible, and
customizable.
It can normalize and check user input in forms, resulting in clean, consistent submissions that
are very user-friendly without unnecessarily constraining the user from entering data in ways that
are convenient for them.
Validator includes the following built-in validation types:
- Required
- Minimum or maximum length
- Pattern (regular expression)
- Numbers (with decimals and negative values)
- Integers (positive whole numbers)
- North American Phone Numbers
- US Zip Codes
- Email Addresses
- Canadian Postal Codes
- Colors (CSS colors, with color picker support)
- Dates (optionally constrained to past or future dates)
- Date and time
- Time of day
- URLs
- Files (type, size, count)
You can also add custom validation and customize error messages per field or for the whole form.
Validator is compatible with all modern browsers. It has no dependencies (other than its validator-utils package). It is written in TypeScript with 100% test coverage.
``bash
npm install @jdlien/validator
pnpm add @jdlien/validator
`
Create a form as you normally would, adding attributes for inputs to control how Validator will
check the input, such as required, type, or data-type. Native HTML5 attributes are supported,data-
although often, the browser's built-in validation is problematic or inflexible. In those cases, you
can use a variant of these attributes to avoid the browser's built-in validation.
Any input that you want to validate should have a unique name attribute. If you want to display error messages for the input, you must also have a div with an id that is the name of the input + -error.
If you're using a bundler:
`javascript`
import Validator from '@jdlien/validator'
If you're using CommonJS:
`javascript`
const Validator = require('@jdlien/validator')
Then, create a new Validator instance and pass it the form element as the first argument. An optional second argument allows you to pass in options. Here is a simplified example:
`html
`
When initialized, Validator disables browser validation and displays error messages in an associated page element. It identifies the appropriate element by searching for an ID in the following order:
1. The value in the input's aria-describedby attribute, if it exists-error
2. The ID of the input + -error
3. The name of the input +
Using the aria-describedby attribute to link an error message with an input is the most robust and effective method. Also, this enhances accessibility by enabling screen readers to announce the error when the input is focused.
If you wish to customize the default error message, you can also set one for a field using data-error-default.
Validator works by checking for certain attributes on the form inputs and applying validation based on those.
In many cases, you can use the native HTML5 attributes, but you can also use the data- attributes if you do not want the behavior to be affected by built-in browser validation behavior (e.g., for min-length, max-length, and input types such as date and time).
There are a few attributes that Validator looks for on the form element:
- data-prevent-submit - If this attribute is present, the form will never be submitted, even if it is valid. This is useful if you want to handle the submission yourself. (By default, the form will be submitted if it is valid and not if it is invalid.)
- novalidate - This is a native HTML5 attribute that disables browser validation on the form. Validator adds this by default and removes it if destroy() is called. If you add it yourself, it will not be added back by Validator.
On input (and sometimes select and textarea) elements, the following attributes are supported:
- required - The input must have a value.minlength
- /data-min-length - The input must be at least the specified number of characters.maxlength
- /data-max-length - The input must be no more than the specified number of characters.pattern
- /data-pattern - The input must match the specified regular expression.type
- /data-type - The input must match the specified type. The following types are supported:
- number (also float/decimal) - The input must be a number (use data-type to avoid quirky browser behavior)integer
- - The input must be a positive whole number.tel
- - The input must be a valid North American phone number.email
- - The input must be a valid email address.zip
- - The input must be a valid US zip code.postal
- - The input must be a valid Canadian postal code.date
- - The input must be a valid date.datetime
- - The input must be a valid date and time.time
- - The input must be a valid time.url
- - The input must be a valid URL.color
- - The input must be a valid CSS color. (This can be used in conjunction with a native color input - see Color Picker Support for details.)
- data-date-format/data-time-format - Applies formatting to date, time, or datetime inputs (these are interchangeable). The format must be a valid moment.js format string. See moment.js docs for more information.data-date-range
- - Applies to date input types. Supported values are past, future, and today.data-min
- /data-max - Applies to numeric input types (number, integer, float, decimal). Validates that the numeric value is within the specified range. Also respects the native min/max attributes, but data- attributes take precedence.data-arrow-step
- - Applies to numeric input types (number, integer, float, decimal). Sets the arrow key step size (defaults to 1). Set data-arrow-step="" to disable arrow key handling for the field.data-error-default
- - A custom error message to display if the input is invalid. This will be used for required, pattern, and date-range validation failures.data-validation
- - The name of a custom validation function.data-novalidate
- - If this attribute is present, the input will not be validated when input or change events are triggered on it.data-max-files
- - Applies to file inputs. Limits the number of files a user can upload.data-min-file-size
- /data-max-file-size - Applies to file inputs. Enforces min/max size per file. Accepts human-readable sizes like 200kb (base 10), 2mib (base 2), 1.5g.accept
- /data-accept - Applies to file inputs. Restricts allowed file types using MIME types and/or extensions. data-accept takes precedence over accept.
A validation function will be called with the input value as the argument. The function may either return a boolean (true/false) or an object with a valid property that is a boolean. If the function returns a string or an object with a message property, that will be used as the error message for the input. A messages array may also be specified which will be used to display multiple error messages for the input.
You may also use a promise that resolves to such an object for asynchronous validation.
There are three ways to register custom validators, listed in order of lookup priority:
#### 1. Instance Registry (Recommended)
Pass validators directly to the Validator constructor. This is the recommended approach as it provides the best type safety and keeps validators scoped to specific forms.
`javascript/api/check-email?email=${encodeURIComponent(value)}
const validator = new Validator(form, {
validators: {
validateUsername: (value) => {
if (value.length < 3) return 'Username must be at least 3 characters'
return true
},
validateEmail: async (value) => {
const res = await fetch()`
return res.ok ? true : 'Email already taken'
},
},
})
#### 2. Static Registry (For Shared Validators)
Use Validator.registerValidator() to make validators available to all Validator instances. Useful for reusable validators across your application.
`javascript
// Register globally (available to all forms)
Validator.registerValidator('validatePhone', (value) => {
return /^\d{10}$/.test(value) ? true : 'Enter a 10-digit phone number'
})
// Later, in any form...
const validator = new Validator(form)
// Input with data-validation="validatePhone" will use the registered validator
// Other static methods:
Validator.unregisterValidator('validatePhone') // Remove a validator
Validator.getValidators() // Get all registered validators (returns a copy)
Validator.clearValidators() // Remove all registered validators
`
#### 3. Window Object (Legacy)
For backward compatibility, validators can be defined on the window object. This is not recommended for new code.
`javascript`
window.validateCustom = (value) => {
return value === 'valid' ? true : 'Invalid value'
}
When resolving a validator by name, Validator checks in this order:
1. Instance registry (validators passed to constructor)
2. Static registry (Validator.registerValidator)
3. Window object (legacy fallback)
This allows you to override global validators for specific forms when needed.
Custom validators are functions you can write that receive the input value and can return:
- true — validfalse
- — invalid (uses default error message)string
- — invalid with custom error message{ valid: boolean, message?: string, messages?: string[] }
- — structured result
All return types can be wrapped in a Promise for async validation.
`javascript
// Simple synchronous validator:
function customValidation(value) {
if (value === 'foo') return 'The value cannot be foo.'
return true
}
// Async validator using fetch:
async function customValidationPromise(value) {
const response = await fetch(/api/validate-username?username=${value})
const { valid, error } = await response.json()
return valid ? true : error
}
// Structured result with multiple errors:
function validatePassword(value) {
const errors = []
if (value.length < 8) errors.push('Must be at least 8 characters')
if (!/[A-Z]/.test(value)) errors.push('Must contain an uppercase letter')
if (!/[0-9]/.test(value)) errors.push('Must contain a number')
return errors.length ? { valid: false, messages: errors } : true
}
`
If any form validation fails on submission, Validator displays a main error message directly beneath the form. By default, it looks for an element with the ID form-error-main. However, if the form itself has an id attribute (e.g.,