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

@georapbox/validated-form

v1.0.0

Published

A Web Component that wraps native HTML form validation and surfaces the browser's validation messages as accessible inline errors.

Readme

npm version npm license

<validated-form>

A Web Component that wraps native HTML form validation and surfaces the browser's validation messages as accessible inline errors. It does not implement validation rules or schemas — it reads the validation state through the Constraint Validation API and displays the control's validationMessage, optionally allowing per-field message overrides via data-msg-* attributes.

The component follows a progressive-enhancement approach: the browser remains responsible for validation, while JavaScript improves how errors are presented and announced. If JavaScript fails to load, the form still works using native browser validation UI.

<validated-form> is a good fit when you want to keep native browser validation and add accessible inline error presentation with minimal configuration.

API documentationDemo

Why?

Modern browsers already provide native form validation using attributes like required, pattern, minlength, maxlength, type="email", and others.

In many cases, that is enough.

However, native validation UI has practical limitations:

  • Error messages appear in browser popups that cannot be styled
  • Messages are not reliably associated with fields for assistive technologies
  • Error bubbles may disappear before users can read them
  • Accessibility behavior differs across browsers

This component builds on native validation rather than replacing it.

Using the Constraint Validation API, it:

  • Reads the browser's validation state
  • Reuses localized validation messages
  • Renders persistent inline errors
  • Associates errors with fields using proper ARIA attributes

This progressive-enhancement approach was informed by writing from Adrian Roselli and HTMHell on the accessibility limitations of default browser validation UI and the benefits of layering accessible feedback on top of native validation.

Usage

Installation

npm install --save @georapbox/validated-form

Importing the component

By default, the package exports the element class without registering it. This lets the application decide when the custom element is defined.

Manual definition

import { ValidatedForm } from '@georapbox/validated-form';

// Define using the default tag name
ValidatedForm.define();

Auto-defined (convenience)

If you don't need control over registration, you can import the pre-defined build which immediately registers <validated-form>.

import '@georapbox/validated-form/define';

Requirements

For the component to function correctly, the markup must follow a few conventions.

1. The element must wrap a <form>

<validated-form> enhances an existing form — it does not create one.

<validated-form>
  <form>
    ...
  </form>
</validated-form>

2. Each validated control must have a name attribute

The component identifies fields using their name attribute (the same identifier used during form submission).

<input type="email" name="email" required>

Controls without a name still participate in validation, but they cannot be associated with an error element, so no inline error message will be shown for them.

3. Each control needs an associated error element

Every field you want to validate must have an element with a data-error-for attribute whose value matches the control's name attribute. If an error element is not provided, the component will create one automatically after the control that triggered validation. For certain controls, such as checkboxes or radio groups, this may place the message between options, so providing the error element in the markup is recommended.

The component manages the visibility of the error element by toggling the hidden attribute based on the validation state.

<input type="email" name="email" required>
<div data-error-for="email" hidden></div>

4. Radio groups should share one error element

Radio buttons with the same name represent a single logical field and must share one error container. Provide a single element with data-error-for="<name>" that matches the group's name attribute.

The component treats the group as a single logical field and uses one shared error container. If the group is invalid, the error message is associated with the radio that triggered validation. On form submission, focus moves to the first invalid radio in the group.

By default (report="all"), the error element is linked to all radios in the group. When using report="first", only the first invalid radio is linked and focused.

For consistent layout and semantics, place the error element after the last radio button in the group.

<label><input type="radio" name="gender" value="m" required> Male</label>
<label><input type="radio" name="gender" value="f"> Female</label>
<div data-error-for="gender" hidden></div>

If an error element is not provided, the component will create one automatically after the radio that triggered validation. For grouped controls this may place the message between options, so providing the element in the markup is recommended.

Notes

  • The error element can be any element (div, span, p, etc.)
  • The component will automatically set the necessary ARIA attributes
  • If JavaScript is unavailable, native browser validation still works
  • The component does not apply any styles — you can style the error elements as needed

Custom Validation Messages

The component supports custom validation messages per input without using setCustomValidity() internally.

Custom messages affect only what <validated-form> displays. They do not change the browser's validation rules, the control's validity state, or native validation UI.

You can provide custom messages using data-* attributes on individual form controls. For each invalid control, the component:

  1. Detects which validation rule failed (via the Constraint Validation API)
  2. Looks for a matching custom message attribute
  3. Falls back to the browser's localized validationMessage if no custom message is provided

This preserves native validation behavior while allowing message customization.

Supported Attributes

The following attributes can be added to form controls:

| Attribute | ValidityState flag | Description | | --------- | --------------- | ----------- | | data-msg-required | valueMissing | The control is required, but no value has been provided. | | data-msg-type | typeMismatch | The value does not match the expected input type, such as email or url. | | data-msg-pattern | patternMismatch | The value does not match the pattern defined by the pattern attribute. | | data-msg-too-short | tooShort | The value is shorter than the length required by the minlength attribute. | | data-msg-too-long | tooLong | The value is longer than the length allowed by the maxlength attribute. | | data-msg-min | rangeUnderflow | The value is less than the minimum allowed by the min attribute. | | data-msg-max | rangeOverflow | The value is greater than the maximum allowed by the max attribute. | | data-msg-step | stepMismatch | The value does not conform to the interval defined by the step attribute. | | data-msg-bad-input | badInput | The browser could not convert the entered value into a valid value for that control type. |

When a control is invalid, the component checks for a matching data-msg-* attribute and uses its value as the error message.

If no matching attribute is present, the browser's default localized message is used.

[!NOTE] For radio groups with a shared error element, apply data-msg-* attributes consistently across the group. With report="all", the message is resolved per radio and the last processed radio determines the final text in the shared error container.

Example markup with custom messages:

<validated-form>
  <form>
    <div>
      <label for="email">Email</label>
      <input
        id="email"
        name="email"
        type="email"
        required
        data-msg-required="Email is required."
        data-msg-type="Please enter a valid email address."
      >
      <div data-error-for="email" hidden></div>
    </div>

    <div>
      <label for="password">Password</label>
      <input
        id="password"
        name="password"
        type="password"
        required
        minlength="8"
        data-msg-required="Password is required."
        data-msg-too-short="Password must be at least 8 characters."
      >
      <div data-error-for="password" hidden></div>
    </div>

    <button type="submit">Submit</button>
  </form>
</validated-form>

How Message Resolution Works

When a control is invalid:

  • The component checks validation flags in priority order.
  • If a matching data-msg-* attribute exists, that message is displayed.
  • Otherwise, it falls back to control.validationMessage.

This ensures:

  • Native validation semantics remain intact.
  • Browser localization is preserved by default.
  • You can override only the rules you care about.

Interaction with setCustomValidity()

Although the component does not use setCustomValidity() internally, consumers can still use it.

If a consumer sets a custom validity message:

input.setCustomValidity('This value is not allowed.');

That message becomes the control's validationMessage and will be displayed when no data-msg-* override applies.

Per-rule data-msg-* attributes still take precedence for the specific rule they match.

API

Properties

| Name | Reflects | Type | Required | Default | Description | | ---- | -------- | ---- | -------- | ------- | ----------- | | noFocusno-focus | ✓ | boolean | - | false | Determines whether the component focuses the first invalid control when validation fails. When false (default), focus moves to the first invalid control. When true, errors are shown without changing focus. | | report | ✓ | 'all' \| 'first' | - | 'all' | Determines how validation messages are reported when the form is validated. Use 'all' to show messages for all invalid controls, or 'first' to show only the first invalid control's message. After validation has started, live updates still reflect the field being edited. |

Methods

| Name | Type | Description | Arguments | | ---- | ---- | ----------- | --------- | | define | Static | Defines the custom element using the provided name. If no name is given, the default tag name is used. If the element is already registered, the method does nothing. | elementName='validated-form' | | validate | Instance | Validates the form, updates the displayed validation feedback, and returns whether the form is valid. | - | | resetValidation | Instance | Resets the component's validation UI by clearing displayed error messages and validation feedback. It does not reset form field values or change the browser's underlying validity state. | - | | isValid | Instance | Returns whether the form is currently valid according to the browser's native validation rules, without showing validation messages. | - |

1 Instance methods are only available after the component has been defined. To ensure the component is defined, you can use whenDefined method of the CustomElementRegistry interface, eg customElements.whenDefined('validated-form').then(() => { /* call methods here */ });

Changelog

For API updates and breaking changes, check the CHANGELOG.

Development setup

Prerequisites

The project requires Node.js and npm to be installed on your environment. Preferrably, use nvm Node Version Manager and use the version of Node.js specified in the .nvmrc file by running nvm use.

Install dependencies

Install the project dependencies by running the following command.

npm install

Build for development

Watch for changes and start a development server by running the following command.

npm start

Linting

Lint the code by running the following command.

npm run lint

Testing

Run the tests by running any of the following commands.

npm test
npm run test:watch # watch mode

Build for production

Create a production build by running the following command.

npm run build

License

The MIT License (MIT)