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

form-control-mixin

v1.1.1

Published

Framework-agnostic mixin for form-associated custom elements: native form submission, validation and reset, with built-in validators, on top of ElementInternals.

Readme

form-control-mixin

npm version npm downloads bundle size license

A small, framework-agnostic mixin for building form-associated custom elements on top of the native ElementInternals API.

Wrap any class extending HTMLElement — a plain custom element, a LitElement, a Stencil or FAST base class — and it will participate in native <form> submission, validation, and reset, without you having to touch ElementInternals directly.

import { FormControlMixin, requiredValidator, minLengthValidator } from 'form-control-mixin';

class MyInput extends FormControlMixin(HTMLElement, {
  validators: [requiredValidator, minLengthValidator(3)],
}) {
  value = '';

  connectedCallback() {
    this.requestValidation();
  }

  formResetCallback() {
    this.value = '';
    this.requestValidation();
  }
}

customElements.define('my-input', MyInput);
<form>
  <my-input name="nickname"></my-input>
  <button>Submit</button>
</form>

new FormData(form).get('nickname') and native constraint validation UI (:invalid, reportValidity(), browser validation bubbles) work out of the box.

What you get

  • No framework, no renderer. It's a mixin over HTMLElement, so it works in a plain custom element, Lit, Stencil, FAST — and the resulting element drops into React, Vue, Angular, Svelte or plain HTML like any other custom element.
  • ~3 KB gzipped, zero runtime dependencies. No bundled polyfill, no assumed rendering layer.
  • TypeScript types shipped (dist/index.d.ts), ESM only.
  • Nine built-in validators mirroring native constraint semantics, plus a three-property interface for your own.
  • :state() styling out of the boxvalid, invalid, touched, dirty exposed as custom states, no attribute reflection.
  • Tested on real engines. The suite runs in Chromium, Firefox and WebKit via Playwright, not a DOM shim.

Demo

Live: https://korial29.github.io/form-control-mixin/demo/

Locally:

npm run dev

Why this exists

Form-associated custom elements have been broadly supported since 2023 (Chromium, Firefox, Safari 16+), but the plumbing around ElementInternals — wiring up value sync, validity flags, reset/disable/restore callbacks — is boilerplate every design system ends up rewriting.

An attempt at solving this generically already exists, @open-wc/form-control, but it hasn't seen a release in about three years and never settled on a finished API. Bigger component libraries (Shoelace, Ionic, FAST) solved the same problem, but only inside their own codebase — not as something you can install if you're building your own design system.

So this fills the gap: the ElementInternals plumbing, on its own, installable.

Install

npm install form-control-mixin

API

FormControlMixin(SuperClass, config?)

  • SuperClass — any class extending HTMLElement.
  • config.valueProperty — name of the host property holding the current value. Defaults to "value".
  • config.validators — ordered list of Validator objects. The first one that fails wins and sets the matching ValidityStateFlags key.

The mixin adds internals, form, validity, validationMessage, willValidate, checkValidity(), reportValidity(), setValidity(), and requestValidation() (re-runs validators and syncs the form value). It also declares (empty, override-friendly) formAssociatedCallback, formDisabledCallback, formResetCallback, and formStateRestoreCallback hooks.

Custom states

requestValidation() also keeps internals.states (a CustomStateSet) in sync, so any consumer can style the host from the outside with the :state() pseudo-class — no attribute reflection needed:

my-input:state(invalid) {
  outline: 2px solid crimson;
}
my-input:state(touched):state(invalid) {
  /* only after the user has actually interacted with the field */
}
  • valid / invalid — mirrors validity.valid.
  • touched — set on the first focusout of the host (or any shadow-DOM descendant); cleared by formResetCallback (call super.formResetCallback() if you override it, to keep this in sync).
  • dirty — set once the value diverges from the value seen on the first requestValidation() call; needs no explicit reset since it's recomputed every call — a formResetCallback override that resets the value and calls requestValidation() clears it automatically.

Built-in validators

requiredValidator, minLengthValidator(n), maxLengthValidator(n), patternValidator(regex, message?), emailValidator, urlValidator, minValidator(n), maxValidator(n), stepValidator(step, base?) — mirror native required, minlength, maxlength, pattern, type="email", type="url", min, max, and step semantics. Write your own by implementing { key, message, isValid(host, value) }.

Contributing

Single-branch model on master (same conventions as lit-pdf-viewer):

  • Branch off master (feature/*, fix/*, chore/*), open the PR back into master.
  • Add exactly one release label: release:major, release:minor, release:patch, or release:skip. Enforced by pr-validation.yml.
  • Merging a release:* PR (not skip) proposes a release via release.yml — tag, npm publish, GitHub Release — gated behind a manual approval (release environment). package.json's version is just a baseline; the real next version comes from the latest git tag.

Before opening a PR: npm run lint && npm run build && npm run test:ci (exactly what CI runs).

Status

Published on npm and in use. The core mixin, the built-in validators, the custom states and the cross-browser test suite are in place; the API is not expected to churn from here.

License

MIT © Ludovic Dupont