npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

a11y-form-validator

v1.0.19

Published

Accessible, dependency-light form validation plugin for semantic HTML forms

Readme

A11y Form Validator

Accessible, dependency-light progressive enhancement for semantic HTML forms.

A11y Form Validator is a progressive enhancement plugin for forms that already use real labels, inputs, selects, textareas, checkboxes, radios, and fieldsets. It reads native validation attributes, supports custom and async rules, applies server errors, renders accessible inline messages, can add a focusable error summary and character counts, supports opt-in locale packs, and cleans up generated DOM when destroyed.

Installation

A11y Form Validator is published on npm as a11y-form-validator.

npm install a11y-form-validator
pnpm add a11y-form-validator
yarn add a11y-form-validator

Usage

import {
  createDefaultPreset,
  createFormValidator
} from "a11y-form-validator";
import "a11y-form-validator/styles.css";

const form = document.querySelector("[data-a11y-form-validator]");

if (form instanceof HTMLFormElement) {
  createFormValidator(form, createDefaultPreset());
}

createDefaultPreset() includes the error summary addon. Use createNoSummaryPreset() when you want the same submit-and-blur validation pattern without summary UI.

new A11yFormValidator(form, options) is also supported for existing integrations. Duplicate initialization of the same form returns the existing instance.

Which build should I use?

Bundler usage

Use the normal package entry when using a bundler such as Vite, Astro, Rollup, Webpack, or similar.

import {
  createDefaultPreset,
  createFormValidator
} from "a11y-form-validator";
import "a11y-form-validator/styles.css";

This gives your bundler the readable ESM build. Your app build process can tree-shake and minify it for production.

Direct browser usage

Use the minified build when importing directly in a browser from a built file or CDN-style setup.

<script type="module">
  import {
    createDefaultPreset,
    createFormValidator
  } from "./dist/index.min.js";

  const form = document.querySelector("[data-a11y-form-validator]");

  if (form instanceof HTMLFormElement) {
    createFormValidator(form, createDefaultPreset());
  }
</script>

For npm subpath usage:

import {
  createDefaultPreset,
  createFormValidator
} from "a11y-form-validator/min";

Rule of thumb:

Using a bundler? Use the normal import.
Using the file directly on a site? Use the minified file.

Build output

npm run build:dist writes both browser-ready ESM files:

  • dist/index.js and dist/index.js.map - readable, source-mapped default package entry.
  • dist/index.min.js and dist/index.min.js.map - optional minified entry for direct browser or CDN-style imports.

Both entries use dist/index.d.ts for types. The package "." export stays on dist/index.js; only the ./min subpath points to dist/index.min.js.

CSS

Import the default styles when you want the provided error, summary, focus, and character count presentation:

import "a11y-form-validator/styles.css";

The main block class is .a11y-form-validator. Public custom properties include:

  • --a11y-form-validator-error-color
  • --a11y-form-validator-border-color
  • --a11y-form-validator-focus-color
  • --a11y-form-validator-success-color
  • --a11y-form-validator-pending-color
  • --a11y-form-validator-error-shadow
  • --a11y-form-validator-gap
  • --a11y-form-validator-radius
  • --a11y-form-validator-font-size
  • --a11y-form-validator-summary-background
  • --a11y-form-validator-character-count-color

State classes such as .is-valid, .is-invalid, .is-pending, .is-touched, .is-dirty, and .is-disabled are applied to controls. Reduced motion is respected for plugin scroll behavior.

HTML Structure

Use normal form markup with labels. Each field needs a name or id.

<form data-a11y-form-validator>
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required />

  <label for="message">Message</label>
  <textarea id="message" name="message" required minlength="10" maxlength="180" data-character-count></textarea>

  <fieldset>
    <legend>Preferred reply method</legend>
    <label><input name="replyMethod" type="radio" value="email" required /> Email</label>
    <label><input name="replyMethod" type="radio" value="phone" required /> Phone</label>
  </fieldset>

  <button type="submit">Send</button>
</form>

Supported native rules include required, type="email", minlength, maxlength, pattern, required checkbox, radio groups, and checkbox groups.

Data attributes can add rules and messages:

<input
  name="username"
  required
  data-validate="min-length:3 pattern:^[a-z0-9_]+$"
  data-message-required="Choose a username."
/>

API

createFormValidator(form, options)

Initializes a form and returns an A11yFormValidatorInstance.

import { createErrorSummaryAddon } from "a11y-form-validator/addons/error-summary";
import { createCharacterCountAddon } from "a11y-form-validator/addons/character-count";

const validator = createFormValidator(form, {
  validateOn: ["submit", "blur", "input"],
  focusOnError: "summary",
  errorMode: "both",
  addons: [createErrorSummaryAddon(), createCharacterCountAddon()]
});

initFormValidators(options, root)

Initializes every form matching [data-a11y-form-validator] under root.

import { createDefaultPreset } from "a11y-form-validator/presets/default";

const validators = initFormValidators(createDefaultPreset());

Options

| Option | Default | Description | | --- | --- | --- | | validateOn | ["submit"] | Events that trigger validation: submit, blur, input, change. | | focusOnError | "summary" | Use "summary", "first-invalid", or false. | | errorMode | "inline" | Use "inline", "native", or "both". | | useNativeRules | true | Reads native HTML validation attributes. | | disableNativeUI | true | Adds novalidate unless native-only mode is used. | | validateHidden | false | Validates hidden fields when true. | | ignore.disabled | true | Skips disabled fields. | | ignore.hidden | true | Skips hidden fields unless validateHidden is true. | | ignore.selector | "" | Skips matching controls. | | debounce | 150 | Debounce delay for input validation. | | messages | {} | Global, field-specific, or function messages. | | locales | {} | Locale message packs keyed by locale code. | | locale | "" | Explicit locale; falls back to form/document language and English. | | selectors.fields | "input, select, textarea" | Field selector used by refresh(). | | addons | [] | Addon objects imported from addon or preset subpaths. | | renderer | null | Custom renderer implementing render(), clear(), and optional destroy(). | | rules | {} | Field-specific rule configuration keyed by field name or id. |

Instance Methods

  • validate(options?)
  • validateField(fieldOrNameOrElement, options?)
  • refresh()
  • reset()
  • clearErrors()
  • setErrors(errors)
  • getErrors()
  • getState()
  • enable()
  • disable()
  • focusOnError()
  • destroy()
  • registerRule(name, rule)
  • unregisterRule(name)

Server errors can be applied with either a field map or explicit fields/form keys:

validator.setErrors({
  fields: {
    email: "This email is already registered."
  },
  form: "Your session expired. Reload the page and try again."
});

Presets And Addons

Preset helpers are available from the main entry and from explicit subpaths. Use explicit subpaths when you want narrower imports:

import { createDefaultPreset } from "a11y-form-validator/presets/default";
import { createNoSummaryPreset } from "a11y-form-validator/presets/no-summary";
import { createMinimalPreset } from "a11y-form-validator/presets/minimal";

createDefaultPreset() validates on submit and blur, uses inline and native messages, imports and installs the error summary addon, and focuses the summary after blocked submits. Because it imports the summary addon, choose it only when you want summary UI.

createNoSummaryPreset() keeps the same submit and blur validation cadence, inline and native messages, native rule reading, and native UI suppression as the default preset, but it installs no addons and focuses the first invalid field.

createMinimalPreset() validates on submit and focuses the first invalid field.

The error summary addon prepends a focusable, labelled summary with field-labelled links to invalid fields and plain-text form-level errors. The character count addon adds a polite live region for controls with maxlength, minlength, or data-character-count.

import { createNoSummaryPreset } from "a11y-form-validator/presets/no-summary";
import { createErrorSummaryAddon } from "a11y-form-validator/addons/error-summary";
import { createCharacterCountAddon } from "a11y-form-validator/addons/character-count";

const preset = createNoSummaryPreset();
createFormValidator(form, {
  ...preset,
  focusOnError: "summary",
  addons: [createErrorSummaryAddon(), createCharacterCountAddon()]
});

Localization

English messages are bundled as the default fallback. Other locale packs are published as opt-in JSON files so they do not increase the main bundle.

import { createDefaultPreset } from "a11y-form-validator/presets/default";
import esMessages from "a11y-form-validator/locales/es.json" with { type: "json" };

createFormValidator(form, {
  ...createDefaultPreset(),
  locale: "es",
  locales: {
    es: esMessages
  }
});

You can also provide inline locale messages during initialization:

createFormValidator(form, {
  locale: "product",
  locales: {
    product: {
      required: "{fieldLabel} is required before you continue."
    }
  }
});

Configured locale lookup uses the explicit locale, then the form or document language, then English fallback. Field-level data-message-* and messages.fields entries still override locale packs. Use summaryItem to customize how summary links combine field labels and messages; the default is {fieldLabel}: {message}.

Events

Events bubble from the form. Event detail includes both instance and validator for compatibility.

| Event | Notes | | --- | --- | | a11y-form-validator:init | Fired after fields, listeners, and addons are installed. | | a11y-form-validator:before-validate | Fired before form validation. | | a11y-form-validator:after-validate | Fired after validation with valid and errors. | | a11y-form-validator:field-valid | Fired when a field validates successfully. | | a11y-form-validator:field-invalid | Fired with field, message, and reason. | | a11y-form-validator:form-valid | Fired when the form is valid. | | a11y-form-validator:form-invalid | Fired when the form is invalid. | | a11y-form-validator:submit-blocked | Fired after a submit is prevented. | | a11y-form-validator:destroy | Fired after cleanup. |

Accessibility Notes

  • The plugin keeps native form controls and labels in place.
  • Inline errors are connected with aria-describedby and aria-errormessage.
  • Generated inline errors use a polite live region for field-level validation.
  • During blocked submits that focus the error summary, generated inline errors stay associated with fields but do not create competing live-region announcements.
  • The summary addon creates a focusable, labelled error summary with field-labelled links to invalid fields and plain-text form-level errors.
  • Focus moves to the summary or first invalid field after a blocked submit, depending on focusOnError.
  • When applying server errors with setErrors(), call focusOnError() if the error summary or first invalid field should be announced immediately.
  • Existing aria-describedby values are preserved and restored when errors clear or the plugin is destroyed.
  • Radio groups and checkbox groups are treated as grouped fields when they share a name.
  • Keyboard behavior remains native: Tab moves through controls and summary links; Enter submits forms or activates links; Space toggles checkboxes and radio buttons.

Still test customized forms with your target browsers and assistive technologies.

Limitations

  • The plugin validates controls collected by selectors.fields; custom widgets need custom rules or a custom renderer.
  • Remote validation is supported through async custom rules, but request cancellation is the application’s responsibility. Pending state is exposed through state, events, and .is-pending; add an application live status such as "Checking..." when users need progress announcements.
  • The default CSS is intentionally minimal and may need product-specific layout styles.
  • Native browser validation messages vary by browser and locale when errorMode includes native behavior.

Examples

Interactive demos are published on GitHub Pages:

Repository demo source is also available on GitHub:

Build the package with npm run build before opening local example or demo pages, because they import from dist. Most demos use the readable dist/index.js; the direct browser minified build demo uses dist/index.min.js.

Build the GitHub Pages branch-publishing output with npm run pages:build. The generated static site is written to docs/ and should be committed with changes that affect the live demos.

GitHub Pages demo build

The Pages artifact copies the full dist/ directory because the readable ESM build may reference generated shared chunks.

Do not copy only dist/index.js. If index.js imports generated chunk files, those files must also be deployed.

The minified demo uses dist/index.min.js, but the regular demos intentionally use dist/index.js to verify the normal package build.

For first-time GitHub Pages deployment, use branch publishing in the repository settings: Settings -> Pages -> Build and deployment -> Source -> Deploy from a branch -> Branch: main -> Folder: /docs. After that setting is saved, pushes to main that include updated docs/ files publish automatically.

Docs Metadata

import { docs } from "a11y-form-validator/docs";

This docs subpath is a package export, not a source folder. The metadata lives in src/docs.ts and is built to dist/docs.js and dist/docs.d.ts.

Development

npm install
npm run typecheck
npm run test
npm run build
npm run pages:build
npm run pack:check

The repository also includes MANUAL_QA.md for final browser, accessibility, privacy, package, and human sign-off checks.

Do not run publish, push, tag, or release commands unless you intend to perform a release.