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.4.0

Published

Form controller for vanilla JS and direct browser usage.

Readme

Forms

A small, framework-free form controller for vanilla JS and direct browser usage.

It binds to an HTMLFormElement, keeps field state in sync with the DOM, runs validation, lets you react to changes via watchers or subscribers, and ships a serialized payload for fetch flows.


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.4.0 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 | Pre-bundled IIFE that registers window.Forms for direct <script> usage. |

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 · 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 |

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