Focus & scroll on invalid submit
The default does the right thing on submit. The imperative helpers exist for when you need to drive focus or scroll outside the submit path.
- Category
- Return methods
- Auto behavior
handleSubmit on invalid → focusFirstError- Helpers
focusFirstError(options?) · scrollToFirstError(options?)- Returns
boolean; true if a target was found
Submit the form with empty fields to watch focus pull to the first invalid one automatically. That's handleSubmit running its default invalid-submit policy ('focus-first-error'). The two buttons below dispatch the helpers imperatively, so you can drive focus or smooth-scroll outside the submit handler. Submitting again with valid fields shows the no-op success path.
Default on invalid submit
handleSubmit pulls focus to the first invalid field on failed submission:
const onSubmit = form.handleSubmit(async (values) => {
await api.send(values)
})
When validation fails, the handler:
- Increments
form.meta.submissionAttempts.form.meta.submittedstaysfalse; it only flips on a successful callback. - Surfaces errors at every invalid path.
- Calls
form.focusFirstError()(the same method exposed below). - Calls
onError(errors)if you passed one.
The "first" invalid field is in schema-declaration order, which matches the visual reading order for most forms (top to bottom, left to right).
focusFirstError(options?)
form.focusFirstError({ preventScroll: false })
Returns true when a target was found and focused, false when no field is in an error state. The optional preventScroll: true skips the browser's default focus-related scroll if you've got a custom scroll strategy.
Reach for this when:
- A page-level error banner has a "Jump to first error" button.
- A multi-step form's "Next" button should pull focus on validation failure without going through
handleSubmit. - Replacing the default invalid-submit policy with custom UX (see Customizing the invalid-submit policy).
scrollToFirstError(options?)
form.scrollToFirstError({ behavior: 'smooth', block: 'center' })
Returns true when a target was found and scrolled into view. Options forward to the underlying Element.scrollIntoView: behavior: 'smooth' for animated scroll, block: 'center' to position the field in the middle of the viewport.
The default invalid-submit policy focuses but doesn't scroll on most browsers (focus triggers a minimal scroll). For tall forms where the first error might be far above the user's current scroll position, layer this on:
const onSubmit = form.handleSubmit(
async (values) => {
/* ... */
},
() => {
form.scrollToFirstError({ behavior: 'smooth', block: 'center' })
}
)
Customizing the invalid-submit policy
Disable the default focus pull at the form level and run your own:
useForm({
schema,
onInvalidSubmit: 'none', // skip the default focus
})
Then drive focus + scroll from onError:
const onSubmit = form.handleSubmit(onSubmitValid, () => {
form.scrollToFirstError({ behavior: 'smooth', block: 'center' })
form.focusFirstError({ preventScroll: true })
})
The 'focus-first-error' (default), 'scroll-to-first-error', 'both', and 'none' policy options live on the form config and on createAttaform({ defaults }) for an app-wide default. See the Types reference for the full set.
Where to next
handleSubmit: the dispatch surface that calls these by default.- Server-side errors: bring API failures back into the same focus / scroll machinery.
- Display state and showing errors: the predicate that decides when errors render.