Skip to content

Getting started

This page walks you through installing @samline/forms and running a working example. By the end you’ll have a bound controller, validated inputs, and a fetch-based submit.

  • Node.js 20+ when bundling (the package is published as ESM + CJS and ships TypeScript types).
  • A package manager — npm, pnpm, yarn or bun.
  • A runtime target of ES2020 in the browser.
Terminal window
npm install @samline/forms

Drop this in any modern bundler or TypeScript project:

contact.html
<form id="contact-form">
<input name="email" type="email" />
<input name="message" />
<button type="submit">Send</button>
</form>
contact.ts
import { form } from '@samline/forms'
const contact = form('contact-form', {
validators: {
email: {
required: { value: true, message: 'Email is required.' },
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'Enter a valid email address.'
}
},
message: { required: true, minLength: 10 }
}
})
contact.onSubmit(async (_element, _data, formData) => {
await fetch('/api/contact', { method: 'POST', body: formData })
})
contact.validate()

What this does:

  • Binds a controller to the form with id contact-form.
  • Adds css-filled and css-error attributes on fields so you can style them with CSS — see CSS styling.
  • Validates email and message on every change and on submit.
  • Intercepts valid submits (the default for onSubmit) and hands a real FormData instance to your handler.
  • Invalid submissions are always intercepted — the browser never navigates with bad data.

If you do not have a bundler, drop a single <script> and use the window.Forms global:

index.html
<form id="contact-form">
<input name="email" type="email" />
<button type="submit">Send</button>
</form>
<script src="https://unpkg.com/@samline/forms@2.2.2/dist/browser/global.global.js"></script>
<script>
const contact = window.Forms.newForm({
id: 'contact-form',
options: {
validators: {
email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }
}
}
})
contact.onSubmit(async (_form, _data, formData) => {
await fetch('/api/contact', { method: 'POST', body: formData })
})
</script>

See the Browser global reference for the full surface, including Forms.newForm, Forms.destroyForm, and the Forms.available registry.

If you have a bundler and want the same { newForm, destroyForm, available } ergonomics as the IIFE — but without window.Forms auto-installed — import the browser singleton from the vanilla entrypoint:

bundler.ts
import { browser } from '@samline/forms'
import { regex } from '@samline/formatter' // optional, from your project
window.Form = { ...browser, regex }
window.Form.newForm({
id: 'contact-form',
options: {
validators: {
email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }
}
}
})
window.Form.destroyForm('contact-form')
console.log(window.Form.available) // { 'contact-form': FormController }

browser is an object you can spread into your own globals or use directly. Because newForm and destroyForm close over the singleton’s available map, every spread shares the same registry — window.Form.available, browser.available, and any other spread all point to the same object, and destroyForm on one updates the others.

If you need multiple independent registries, call the form() factory directly and keep your own map of controllers — browser is designed for the common case of one registry per page.

See the Browser global reference → Using the same shape from a bundler for more, and the TypeScript reference → FormsApi for the exact shape.

Configuration

Every option, default, and rule type — attributes, autoValidate, autoSubmit, validators, and more.

Configuration →

API reference

Function-by-function documentation with TypeScript signatures, return shapes, and behaviour tables.

API reference →

TypeScript

Every exported type — FormController, FormControllerOptions, ValidationSchema, callbacks, helpers.

TypeScript reference →

Examples

End-to-end patterns: login, server errors, multi-step, autosave, formatting, and SPA cleanup.

Browse examples →