A web component that stores (and restores) values within the first form it wraps.
It is designed for crash recovery and interrupted sessions:
- Debounces saves to
localStorageas users type. - Restores values when the page is loaded again.
- Clears saved values after a successful submit flow.
- Optionally retains selected fields after submit.
- Never saves or restores password fields.
Additional demos:
npm install @aarongustafson/form-saverImport the package to automatically define the <form-saver> custom element:
import '@aarongustafson/form-saver';Or use the define-only script in HTML:
<script src="./node_modules/@aarongustafson/form-saver/define.js" type="module"></script>Import the class and define the custom element with your preferred tag name:
import { FormSaverElement } from '@aarongustafson/form-saver/form-saver.js';
customElements.define('my-custom-name', FormSaverElement);<form-saver>
<form action="/contact" method="post">
<label>
Name
<input name="name" autocomplete="name" />
</label>
<label>
Email
<input name="email" type="email" autocomplete="email" />
</label>
<button type="submit">Send</button>
</form>
</form-saver>The component targets the first descendant <form>.
Use retain to keep specific field names (or ids when name is missing) after submit:
<form-saver retain="name email">
<form action="/contact" method="post">
<input name="name" />
<input name="email" type="email" />
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>
</form-saver>In this example, message is cleared after submit while name and email remain.
To inject an opt-in checkbox, add retain-choice.
<form-saver
retain="name email"
retain-choice
retain-choice-label="Store my contact information for later"
>
<form action="/contact" method="post">
<input name="name" />
<input name="email" type="email" />
<button type="submit">Send</button>
</form>
</form-saver>By default, the checkbox is inserted just before the first submit control.
You can control placement with retain-choice-container, which accepts a CSS selector. The matched element is used as a container — the checkbox is appended inside it:
<form-saver
retain="name email"
retain-choice
retain-choice-container=".retain-slot"
>
<form action="/contact" method="post">
<input name="name" />
<input name="email" type="email" />
<div class="retain-slot"></div>
<button type="submit">Send</button>
</form>
</form-saver>| Attribute | Type | Default | Description |
|---|---|---|---|
retain |
string |
"" |
Space-separated field names (or ids when name is missing) to retain after successful submit |
retain-choice |
boolean |
false |
Inject an opt-in retention checkbox |
retain-choice-label |
string |
"Store my contact information for later" |
Plain-text label for the injected checkbox |
retain-choice-container |
string |
"" |
CSS selector for a container element into which the checkbox is appended |
storage-key |
string |
derived | Optional storage key override |
Default derived storage key format:
form-saver:{method}:{resolvedActionUrl}Where method and action come from the wrapped form.
| Method | Description |
|---|---|
saveFormState() |
Serializes and persists current form values |
restoreFormState() |
Applies saved values to current fields |
clearSavedData() |
Removes saved values for the current form key |
Calls to saveFormState() save immediately. Saves triggered by input and
change events use a short debounce to avoid serializing the complete form and
writing to localStorage for every keystroke. Pending changes are flushed when
the page is hidden.
Supported controls include:
input(excepttype="password",file,submit,button,reset, andimage)textareaselect(single and multiple)
File and password fields are always excluded. Password values are neither saved
nor restored, even if their field names appear in retain.
Saved values are stored as plain text in localStorage. Any JavaScript running
on the same origin can read them, so avoid wrapping forms that collect other
sensitive information that should not persist on the device. The component
automatically removes identifiable password entries written by earlier
versions when it reads saved state.
If a browser blocks localStorage, its quota is exhausted, or another storage
operation fails, the component dispatches a bubbling, composed
form-saver-error event. Its detail contains:
{
operation: 'read' | 'write' | 'remove',
error: Error
}For example:
document.querySelector('form-saver').addEventListener(
'form-saver-error',
({ detail }) => {
console.error(`Could not ${detail.operation} saved form data`, detail.error);
},
);This component uses modern web standards:
- Custom Elements v1
- Light DOM (no shadow root)
- ES Modules
For older browsers, you may need polyfills.
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Lint code
npm run lint
# Format code
npm run format
# View demo
open demo/index.htmlMIT © Aaron Gustafson