Offline-Friendly Forms

Forms on the web don't usually play nice with bad connections. If you try to submit a form while offline, you'll most likely just lose your input. Here's how we might fix that.

TL;DR: Here’s the CodePen Demo of this post.

With the introduction of Service Workers, developers are now able to supply experiences on the web that will work even without an internet connection. While it’s relatively easy to cache static resources, things like forms that require server interaction are harder to optimize. It is possible to provide a somewhat useful offline fallback though.

First, we have to set up a new class for our offline-friendly forms. We’ll save a few properties of the <form> element and then attach a function to fire on submit:

class OfflineForm {
// setup the instance.
constructor(form) { =;
this.action = form.action; = {};

form.addEventListener('submit', e => this.handleSubmit(e));

In the submit handler, we can include a simple connectivity check using the navigator.onLine property. Browser support for it is great across the board, and it’s trivial to implement.

⚠️ There is however a possibility of false positives with it, as the property can only detect if the client is connected to a network, not if there’s actual internet access. A false value on the other hand can be trusted to mean “offline” with relative certainty. So it’s best to check for that, instead of the other way around.

If a user is currently offline, we’ll hold off submitting the form for now and instead store the data locally.

handleSubmit(e) {
// parse form inputs into data object.

if (!navigator.onLine) {
// user is offline, store data on device.
} else {
// user is online, send data via ajax.

Storing the Form Input

There are a few different options on how to store arbitrary data on the user’s device. Depending on your data, you could use sessionStorage if you don’t want the local copy to persist in memory. For our example, let’s go with localStorage.

We can timestamp the form data, put it into a new object and then save it using localStorage.setItem. This method takes two arguments: a key (the form id) and a value (the JSON string of our data).

storeData() {
// check if localStorage is available.
if (typeof Storage !== 'undefined') {
const entry = {
time: new Date().getTime(),
// save data as JSON string.
localStorage.setItem(, JSON.stringify(entry));
return true;
return false;

Hint: You can check the storage in Chrome’s devtools under the “Application” tab. If everything went as planned, you should see something like this:

chrome devtools showing the localstorage contents

It’s also a good idea to inform the user of what happened, so they know that their data wasn’t just lost.
We could extend the handleSubmit function to display some kind of feedback message.

feedback message explaining the offline state
How thoughtful of you, form!

Checking for Saved Data

Once the user comes back online, we want to check if there’s any stored submissions. We can listen to the online event to catch connection changes, and to the load event in case the page is refreshed:

window.addEventListener('online', () => this.checkStorage());
window.addEventListener('load', () => this.checkStorage());

When these events fire, we’ll simply look for an entry in the storage matching our form’s id. Depending on what type of data the form represents, we can also add an “expiry date” check that will only allow submissions below a certain age. This might be useful if we only want to optimize for temporary connectivity problems, and prevent users from accidentally submitting data they entered two months ago.

checkStorage() {
if (typeof Storage !== 'undefined') {
// check if we have saved data in localStorage.
const item = localStorage.getItem(;
const entry = item && JSON.parse(item);

if (entry) {
// discard submissions older than one day. (optional)
const now = new Date().getTime();
const day = 24 * 60 * 60 * 1000;
if (now - day > entry.time) {

// we have valid form data, try to submit it. =;

The last step would be to remove the data from localStorage once we have successfully sent it, to avoid multiple submissions. Assuming an ajax form, we can do this as soon as we get a successful response back from the server. We can simply use the storage object’s removeItem() method here.

sendData() {
// send ajax request to server,
.then((response) => {
if (response.status === 200) {
// remove stored data on success
.catch((error) => {

If you dont want to use ajax to send your form submission, another solution would be to just repopulate the form fields with the stored data, then calling form.submit() or have the user press the button themselves.

☝️ Note: I’ve omitted some other parts like form validation and security tokens in this demo to keep it short, obviously these would have to be implemented in a real production-ready thing. Dealing with sensitive data is another issue here, as you should not store stuff like passwords or credit card data unencrypted locally.

If you’re interested, check out the full example on CodePen:

Offline Form by Max Böck on CodePen.


  1. Frontend developer Max Böck some time ago posted an article on how to create offline-friendly web forms Using the navigator.onLine method for checking if the user is still connected to the Internet, localStorage and service workers Max outlines a method for alleviating an annoying problem that comes with using forms on the web, particular on mobile platforms: Losing data and having to fill in a form repetitively if you lose your Internet connection before that form has been submitted. Having forms retain data in offline situations hence provides tremendous usability benefits. February 25, 2018 by Bjoern in Front End Development, Software, Web Applications 0 CommentsTagged with: frontend, html5, javascript, progressive web apps, pwa, web applications
  2.   Look what we’ve got here! The new digest is about Progressive Web Apps, Node.js, WordPress Plugins, JS Frameworks, DevTools, Web Animations, and more. It includes everything you might have missed from the Front-End world last week. Enjoy!   Web Development Node.js Forked Again Over Complaints of Unresponsive Leadership What do you know about viewport height in 2017? A Front-End Developer on Vacation Offline-Friendly Forms Form Validation with Web Audio Fancy Web Animations Made Easy with GreenSock Plugins AMP/PWA: A Progressive Roadmap for your Progressive Web App Android Oreo takes a bite out of Progressive Web Apps Low Hanging PWA Fruit: Manifest Files and Service Worker Precache Our smooth roadtrip to AMP Performance: Managing CSS & JS in an HTTP/2 World Time To First Byte (TTFB) Performance Is About People, Not Metrics [Velocity 2017] Tools: Polymer 3.0 preview: npm and ES6 Modules Custom Elements Everywhere – A project that shows interoperability issues with Custom Elements and other frameworks, and highlights potential fixes. SVG Gradient Wave Generator Aperitif is a custom HTML boilerplate designer allowing you to rapidly build HTML templates and prototypes Chrome 61 – What’s New in DevTools Accessibility: What’s New in WCAG 2.1 WordPress Plugins to Improve Website Accessibility How to structure headings for web accessibility   CSS Inside a super fast CSS engine: Quantum CSS (aka Stylo) detect_flex – Simple and guaranteed method for determination support of FLEX and BOX in any browser. Continuing a series of Visual References on @CodePen about CSS Transforms: Explaining coordinate systems Improve Web Typography with CSS Font Size Adjust So you need a CSS utility library? 4 CSS techniques that will work in any CSS preprocessor, any framework Create a responsive layout with CSS Grid Sass Maps, Loops, and CSS word scrambling!   JavaScript How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code Thinking JavaScript Pretty algorithms – Common useful algorithms written in modern, pretty and easy-to-understand Javascript along with real-world usage examples. JavaScript Is Eating The World 8 most popular JavaScript frameworks As of now Converting from Speech to Text with JavaScript ES2015+: async & await Javascript — ES8 Introducing `async/await` Functions Async/Await in JavaScript, a video on CodeWorkr. VueJS: Vue.js + Brunch: The Webpack Alternative You’ve Been Hungry For 4 AJAX Patterns For Vue.js Apps React: If you’re a startup, you should not use React (reflecting on the BSD + patents license) From Angular zero to React hero in 3 days How do you separate components? Introducing downshift for React . is the primitive you need to build simple, flexible, WAI-ARIA compliant React autocomplete/typeahead/dropdown/select/combobox/etc (AKA “item selection”) (p)react components. From PayPal. Angular: Angular — Applying Motion principles to a listing Preloading ngrx/store with Route Guards Create Advanced Components in Angular Unit testing with Angular and ineeda Automated Documentation for your Typescript/Angular projects Libs & Plugins: Pts.js enables you to compose and visualize points in spaces. JQuery.myData — Small JQuery&Zepto plugin for two-ways data binding. media-player – a tiny, responsive, international, accessible, cross browser, easily customizable media player written in plain vanilla JavaScript. fuzzysort — fast SublimeText-like fuzzy search for JavaScript. fitty — makes text fit perfectly textics — counts lines, words, chars and spaces for a given string  
  3. NAGW
    Offline-Friendly Forms… #nagwgoodies
  4. Digital Marketing
    User submits form. Uh oh, `navigator.onLine` reports `false`. Tell user, and put submitted data into `localStorage`. Submit when back online.…
    Offline forms with #pwa.…

Other things I've written