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 Attaform's default invalid-submit nudge. 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:
- Surfaces errors at every invalid path.
- Calls
form.focusFirstError()(the same method exposed below). - Calls
onError(errors)if you passed one. - Increments
form.meta.submissionAttempts, last, so a read insideonErrorstill sees the previous count.form.meta.submittedstaysfalseeither way; it only flips on a successful callback.
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 automatic nudge with custom UX (see Driving it yourself).
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 automatic nudge 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' })
}
)
Driving it yourself
focusOnInvalidSubmit turns the automatic pull off at the form level:
const form = useForm({
schema,
focusOnInvalidSubmit: false,
})
Then drive focus and scroll from onError:
const onSubmit = form.handleSubmit(onSubmitValid, () => {
form.scrollToFirstError({ behavior: 'smooth', block: 'center' })
form.focusFirstError({ preventScroll: true })
})
Turning the automatic nudge off never takes the helpers away: focusFirstError() and scrollToFirstError() do exactly what they say whatever the form was configured with. That is the point of the off-switch, because the only reason to reach for it is that you want to run the move yourself.
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.