@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 forfetchflows.
Table of Contents
- Installation
- CDN / Browser
- Entrypoints
- Quick Start
- What You Can Build
- API at a Glance
- Documentation
- License
Installation
npm install @samline/formspnpm add @samline/formsbun add @samline/formsRequires 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.0with 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-errorattributes on fields so you can style them with CSS. - Validates
emailon every change and on submit. - Intercepts valid submits (the default for
onSubmit) and hands a realFormDatainstance to your handler.
What You Can Build
- Contact, newsletter, login, signup, checkout, and profile forms.
- Forms that submit with
fetchwhile keeping nativeFormDatapayloads. - Autosave / autosubmit flows with optional debounce.
- Visual feedback driven by
css-filledandcss-errorattributes. - 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/formatterIf 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
