Focus On Errors 🧐
Without a doubt, one of the most common questions I hear from people using my forms libraries is, "How can I focus on the first field that has an error?"
Without a doubt, one of the most common questions I hear from people using my forms libraries is:
“How can I focus on the first field that has an error?”
Merely asking this question demonstrates a perceptive level of UX intelligence. Clearly it’s an order of magnitude better to actually take a user to the problem spot rather than just tell them “There’s an error up there somewhere. Scroll up and try to find it!”
Here’s the answer I usually give:
Well, that’s a tough problem, because calling focus() on an input requires having direct access to the DOM instance, and [form library] does not have that; all [form library] does is provide a
value
andonChange
(and other listeners) to you, the developer that actually renders the<input>
, and there is no good way for you, who may have a DOM reference to the input, to give that back to [form library] to allow [form library] to call focus() on the DOM element.
🙄 Just typing that practically put me to sleep. Yawn! 😴
🤔 Can we do better?
It ocurred to me that, at least in DOM-land (sorry React Native folks!), we have
this ancient structure which, like Seinfeld — “What’s the deal with render
props?!” — dates back to the early 1990’s, that modern web developers might not
know about. Did you know that document.forms[0][0] will give you a reference
to the first input on the first form on your page? Or, that if you give your
form a name, e.g. <form name="foo">
, that you can iterate through an
HTMLCollection
of its inputs at document.forms.foo
? 🤯
So, maybe a higher level form library can gain access to the input DOM elements, as long as they are given the name value that matches the field name the form library knows about?
🥁 🥁 🥁 Introducing, the alliterative…
🏁 Final Form Focus 🧐
🏁 Final Form Focus 🧐 is a
decorator for 🏁 Final Form. That means that you just import it and plug it in,
and it does all the rest. There is only one possible change in configuration: in
the interest of future proofing it and maybe allowing non-DOM platforms, like
React Native, to take advantage of it, you may provide a getInputs()
function
that will give a list of “focusable inputs” (i.e. objects with a name
property
and a focus()
method) that correspond to the fields in your form. For those of
us in DOM land, you can omit that parameter and it will default to searching
through your document.forms
structure and will "just work".
Whenever your form is submitted and the submission fails due to synchronous,
asynchronous, or submission validation errors, the first input on the page that
has a name
prop that matches the path to an error will have focus()
called
on it.
What does it look like? Well, if you’re using 🏁 React Final Form, you just add the decorator like this:
import React from 'react'import { Form, Field } from 'react-final-formimport createDecorator from 'final-form-focus'const focusOnErrors = createDecorator()...<FormonSubmit={submit}decorators={[ focusOnErrors ]} // <--------- 😎validate={validate}render={({ handleSubmit }) =><form onSubmit={handleSubmit}>... inputs here ...</form>}/>
Let’s see it in action:
Vanilla JS Code
For those of you that have arrived to this post by googling “how to focus on the input with the first validation error” (Look, ma! I’m SEOing!!), or looking to add this functionality to your own form solution, this is all it takes:
function focusOnFirstError(formName, hasError) {const form = document.forms[formName];for (let i = 0; i < form.length; i++) {if (hasError(form[i].name)) {form[i].focus();break;}}}
Conclusion
Almost none of the web forms — or native forms, for that matter — I use on a daily basis are sophisticated enough to bring focus to the first field with an error, and thus introduce friction and frustration into the user experience. Implementing “focus on first error” manually on every form is quite difficult, e.g. keeping track of all the DOM refs, sorting them by which is first on the page, and examining the error structure at submission to determine which one to focus on.
But now, with 🏁 Final Form, there is a pluggable way to get that functionality on every form you write.
❤️ Thanks for reading, and I look forward to your feedback! ❤️