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

@samline/forms

v2.7.2

Published

TypeScript-first, framework-free form controller with validation, reactive state, serialization, auto-submit, and input formatting.

Readme

Forms

A TypeScript-first, framework-free controller for native HTML forms.

Add validation, reactive field state, accessible error handling, serialization, auto-submit, and optional input formatting without adopting a UI framework.


Table of Contents


Installation

npm install @samline/forms
pnpm add @samline/forms
bun add @samline/forms

Requires Node 20+ when bundling. Runtime target is ES2020.


CDN / Browser

Use the browser build when you do not have a bundler and need to run the package directly in HTML, Shopify, WordPress, or any traditional template.

<script src="https://unpkg.com/@samline/[email protected]/dist/browser/global.global.js"></script>

Pin the version in production. Replace 2.7.2 with the version you ship.

The browser bundle exposes a single global: window.Forms.

<form id="contact-form">
  <input name="email" type="email" />
  <button type="submit">Send</button>
</form>

<script src="https://unpkg.com/@samline/[email protected]/dist/browser/global.global.js"></script>
<script>
  const contactForm = window.Forms.newForm({ id: 'contact-form' })

  contactForm.onSubmit(async (form, data, formData) => {
    await fetch('/api/contact', { method: 'POST', body: formData })
  })

  contactForm.validate()
</script>

The browser surface keeps a small registry under Forms.available, keyed by the id you pass to Forms.newForm. Each successful newForm call stores the returned controller there, and Forms.destroyForm(id) calls destroy() and removes the entry. Use Forms.form directly when you need the factory without the registry side-effect.

See docs/browser.md for the full browser surface.


Entrypoints

| Entrypoint | When to use | | --- | --- | | @samline/forms | Main vanilla API for bundlers, ESM, or CJS consumers. | | @samline/forms/browser | ESM/CJS browser module with registry exports, bundled types, and window.Forms registration. | | @samline/forms/browser/global | Pre-bundled IIFE for direct <script> usage; the CDN URL resolves to dist/browser/global.global.js. |

The vanilla entrypoint also exports browser, the same { form, newForm, destroyForm, available } surface as the IIFE but as a module-level singleton (no globalThis side-effect). Use it from a bundler when you want the registry helpers without the IIFE — see docs/browser.md → Using the same shape from a bundler.


Quick Start

import { form } from '@samline/forms'

const contactForm = form('contact-form', {
  validators: {
    email: {
      required: true,
      pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    }
  }
})

contactForm.watch('email', value => {
  console.log('email is now:', value)
})

contactForm.onSubmit(async (_element, _data, formData) => {
  await fetch('/api/contact', { method: 'POST', body: formData })
})

What this does:

  • Binds to the form with id contact-form.
  • Adds css-filled / css-error attributes on fields so you can style them with CSS.
  • Validates email on every change and on submit.
  • Intercepts valid submits (the default for onSubmit) and hands a real FormData instance to your handler.

What You Can Build

  • Contact, newsletter, login, signup, checkout, and profile forms.
  • Forms that submit with fetch while keeping native FormData payloads.
  • Autosave / autosubmit flows with optional debounce.
  • Visual feedback driven by css-filled and css-error attributes.
  • Progressive enhancement on top of any existing HTML form.
  • Forms rendered server-side (Blade, Twig, ERB) that still want client-side validation.

API at a Glance

The controller is built around one factory and a small set of focused methods. Most methods are chainable.

| Group | Methods | | --- | --- | | Lifecycle | form · addCleanup · destroy · reset | | Registry (vanilla) | browser — bundler-friendly { form, newForm, destroyForm, available } singleton. | | Properties | element · options | | Submission | onSubmit · autoSubmit · disableAutoSubmit | | Field observation | watch · observe · unwatch · subscribe | | Field values | setValue · getValue · getField · prefill · format · formatAll | | Validation | validate · revalidate · setErrors · clearErrors | | State and data | getData · getState · append | | Pure helpers | parseFormData · validateValues · validateFieldValue |

Validation supports explicit cross-field dependencies with dependsOn, per-member collection rules with each, and strict decimal numeric / inclusive min / max rules. Async onSubmit handlers are reflected by getState().isSubmitting, while addCleanup() lets integrations register controller-owned teardown work.

See the full per-method reference in docs/api/.


Optional peer: @samline/formatter

format and formatAll rely on the optional peer dependency @samline/formatter. Install it when you need input masks (phone, credit-card, date, time, numeral, general):

npm install @samline/formatter

If the peer is not installed, the methods log a single console.error explaining how to install it and return the controller unchanged — the rest of the form keeps working. See docs/recipes.md → 13. Format inputs with @samline/formatter for end-to-end examples.


Documentation

Full API reference, guides, and examples are available at samline.github.io/forms.

| Doc | Purpose | | --- | --- | | docs/getting-started.md | Concepts, observable contract, lifecycle, and side-effect overview. | | docs/options.md | Full FormControllerOptions reference. | | docs/css-styling.md | css-filled and css-error styling recipes. | | docs/typescript.md | Every exported TypeScript type, with examples. | | docs/api/index.md | One page per public method. | | docs/recipes.md | End-to-end patterns: fetch submit, server errors, autosave, multi-step, etc. | | docs/browser.md | Browser global (window.Forms) usage. |


License

MIT