# `<validation-enhancer>`

The easiest, most lightweight validation system.

```bash
npm install validation-enhancer
yarn add validation-enhancer
```

Try the **[LIVE DEMO](https://alistairldavidson.gitlab.io/validation-enhancer/demo/)**!

## Enhance

Wrap any HTML form in `<validation-enhancer>` to enhance its validation.

`<validation-enhancer>` is a web component that brings native HTML validation to life - with the UX you want right out of the box

`<validation-enhancer-zod>` is the same thing, but zod validates the form

### Easy to Use!

- Literally just wrap a form in `<validation-enhancer>`
- Or wrap a form in `<validation-enhancer-zod>` and pass your zod schema to `setZodSchema()`

### Great UX!

- Displays error messages next to each input
- Shows errors on focus out and submit
- Clears errors immediately when the user corrects their input
- Works with screen readers out of the box
- Focuses the first invalid field on submit, and scrolls to its label
- Progressive enhancement as standard - in the event of an error browser html validation will still function

### Customisable!

- Adds `.valid` / `.invalid` CSS classes on inputs as the user interacts, you control the style
- Custom error messages in HTML attributes
- Completely customisable validation using `setCustomValidity()`
- Validate custom elements too!

## Step 1
Wrap `<validation-enhancer>` around your form

```html
<validation-enhancer>
  <form>

    <label for="email">Email</label>
    <input type="email" id="email" required />

    <button type="submit">Submit</button>
  </form>
</validation-enhancer>
```

## Step 2
Assign each input an error display element using the `aria-errormessage` attribute

```html
<validation-enhancer>
  <form>

    <label for="email">Email</label>
    <input type="email" id="email" required aria-errormessage="email-error" />
    <div id="email-error"></div>
    
    <button type="submit">Submit</button>
  </form>
</validation-enhancer>
```

## Step 3

Style `.valid`, `.invalid`, and the validation message.

```css
.valid {
  border-color: green;
}

.invalid {
  border-color: red;
}
```

## Done!

That's all it takes to enhance your form!

```html
<validation-enhancer>
  <form>

    <label for="email">Email</label>
    <input type="email" id="email" aria-errormessage="email-error" required />
    <div id="email-error" style="color:red"></div>
    
    <button type="submit">Submit</button>
  </form>
</validation-enhancer>

<style>
.valid {
  border-color: green;
}

.invalid {
  border-color: red;
}
</style>

<script type="module" src="validation-enhancer.min.js"></script>
```

## Zod integration

Use a [Zod](https://zod.dev) schema as the single source of truth for validation. The zod schema will override any HTML attributes.

```html
<validation-enhancer-zod>
  <form>
    
    <label for="email">Email</label>
    <input type="email" name="email_field" required aria-errormessage="email-error" />
    <div id="email-error"></div>

    <button type="submit">Submit</button>
  </form>
</validation-enhancer-zod>

<script>
import { z } from "zod";

/* Define Zod Schema */
const schema = z.object({
  /* Reference inputs by name attribute */
  email_field: z.string()
    .min(1, "Email is required.")
    .email("Please enter a valid email address."),
});

/* Run when the script is loaded */
customElements.whenDefined("validation-enhancer-zod").then(() => {
  const validationEnhancerZod = document.querySelector("validation-enhancer-zod");
  validationEnhancerZod.setZodSchema(schema);
});
</script>
```

Inputs are matched to schema fields by their `name` attribute — inputs without a `name` are skipped by zod validation.

Use `getZodSchema()` to read back the current schema.

## Install

```bash
npm install validation-enhancer
yarn add validation-enhancer
```

## Import

```js
import "validation-enhancer";
import "validation-enhancer-zod";
```

Or copy `dist/validation-enhancer.min.js` into your project and load it directly:

```html
<script type="module" src="validation-enhancer.min.js"></script>
<script type="module" src="validation-enhancer-zod.min.js"></script>
```

## Custom errors

```html
<input
  type="email"
  id="email"
  required
  aria-errormessage="email-error"
  validation-valueMissing="An email address is required."
  validation-typeMismatch="That doesn't look like a valid email."
>
```

Override the browser's default validation text by adding attributes that match [validity state keys](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState). Both `validation-*` and `data-validation-*` forms are accepted.

Attributes are checked in the following order, to mimic default browser behaviour. Bare attributes are checked before `data-` attributes.

| Attribute                        | Triggered when                        |
| -------------------------------- | ------------------------------------- |
| `validation-valueMissing`        | Required field is empty               |
| `validation-typeMismatch`        | Value doesn't match `type` (eg email) |
| `validation-patternMismatch`     | Value doesn't match `pattern`         |
| `validation-tooLong`             | Value exceeds `maxlength`             |
| `validation-tooShort`            | Value is shorter than `minlength`     |
| `validation-rangeUnderflow`      | Number is below `min`                 |
| `validation-rangeOverflow`       | Number is above `max`                 |
| `validation-stepMismatch`        | Number doesn't match `step`           |
| `validation-badInput`            | Browser can't parse the input         |

If no custom message is set, the browser's built-in `validationMessage` is used.

`<validation-enhancer-zod>` gets its error messages from the zod schema instead.

### Custom message prefix

The default prefix is `"validation"`. Change it with the `message-prefix` attribute to avoid conflicts or match your own naming convention:

```html
<validation-enhancer message-prefix="custom">
  <form>
    <input
      required
      custom-valueMissing="Please fill this out."
      aria-errormessage="msg"
    >
    <!-- also accepts data-custom-valueMissing -->
    <div id="msg"></div>
  </form>
</validation-enhancer>
```

## CSS classes

The component adds these classes to each validated input and also manages `aria-invalid`:

| Class / attribute     | When                       |
| --------------------- | -------------------------- |
| `.valid`              | Input passes validation    |
| `.invalid`            | Input fails validation     |
| `aria-invalid="true"` | Input fails validation     |

Style them however you like:

```css
.valid {
  border-color: green;
}

.invalid {
  border-color: red;
}
```


## Custom class names

By default the component uses `.valid` and `.invalid`. Override them with the `valid-class` and `invalid-class` attributes:

```html
<validation-enhancer valid-class="ok" invalid-class="error">
  <form>
    <!-- inputs will receive ".ok" / ".error" when validating -->
  </form>
</validation-enhancer>
```

```css
input.ok    { border-color: green; }
input.error { border-color: red; }
```

## Custom aria-live urgency

By default the component sets `aria-live="polite"` on error message elements. Override it with `message-aria-live`:

```html
<validation-enhancer message-aria-live="assertive">
```

## Custom message target attribute

By default, the component locates each input's companion message element via `aria-errormessage`. If your markup uses a different attribute, set `message-target-attr`. For example, some older screenreaders might respond better to "aria-describedby" than the newer "aria-errormessage":

```html
<validation-enhancer message-target-attr="aria-describedby">
  <form>
    <input required aria-describedby="email-error">
    <div id="email-error"></div>
  </form>
</validation-enhancer>
```

Inputs that lack the configured attribute fall back to the browser's native `reportValidity()`.

## Scroll behaviour

On submit, focuses the first invalid field and scrolls to its label. The scroll can be configured with attributes on `<validation-enhancer>`:

| Attribute            | Effect                                                                 |
| -------------------- | ---------------------------------------------------------------------- |
| `scroll-behavior`    | `ScrollBehavior` string passed to `scrollIntoView`, default `"smooth"` |
| `scroll-block`       | `block` option for `scrollIntoView`                                    |
| `scroll-inline`      | `inline` option for `scrollIntoView`                                   |
| `scroll-align-to-top`| When present, passes a boolean to `scrollIntoView` instead of options object (`"false"` → `false`, anything else → `true`) |
| `scroll-container`   | CSS selector for a custom scroll container — scrolls that element instead of calling `scrollIntoView` |

## Programmatic value changes

The component listens for `change`, `focusout`, and `keyup` events. If you set an input's value in JavaScript, dispatch a `change` event so validation picks it up:

```js
input.value = "new value";
input.dispatchEvent(new Event("change", { bubbles: true }));
```

The web component registers newly-added forms and fields, if they are modified after instantiation.

## Versions

`dist/validation-enhancer.js`: Unminified, for development

`dist/validation-enhancer.min.js`: Minified, for production

`dist/validation-enhancer.compat.min.js`: ES2017 build minified, for production - compatible with older browsers

`dist/validation-enhancer-zod.js`: Comes with main validation-enhancer included. Unminified, for development

`dist/validation-enhancer-zod.min.js`: Comes with main validation-enhancer included. Minified, for production

`dist/validation-enhancer-zod.compat.min.js`: Comes with main validation-enhancer included. ES2017 build minified, for production - compatible with older browsers

## Development

```bash
npm install        # install dependencies
npm run build      # compile to dist/
npm run demo       # open demo page
npm test           # run tests (vitest)
npm run test:e2e   # run tests (playwright)
npm run lint       # eslint
npm run format     # prettier --write
```
