[{"data":1,"prerenderedAt":563},["ShallowReactive",2],{"content-\u002Fdocs\u002Fsubmitting\u002Ffocus-scroll":3},{"id":4,"title":5,"body":6,"description":541,"extension":542,"meta":543,"metaRows":544,"navigation":557,"path":558,"seo":559,"source":560,"stem":561,"__hash__":562},"docs\u002Fdocs\u002Fsubmitting\u002Ffocus-scroll.md","Focus & scroll on invalid submit",{"type":7,"value":8,"toc":534},"minimark",[9,13,20,23,35,40,45,50,125,128,163,166,172,193,207,210,231,237,264,281,284,371,374,377,413,420,477,502,506,530],[10,11,5],"h1",{"id":12},"focus-scroll-on-invalid-submit",[14,15,16],"blockquote",{},[17,18,19],"p",{},"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.",[21,22],"docs-meta-table",{},[17,24,25,26,30,31,34],{},"Submit the form with empty fields to watch focus pull to the first invalid one automatically. That's ",[27,28,29],"code",{},"handleSubmit"," running its default invalid-submit policy (",[27,32,33],{},"'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.",[36,37],"docs-demo",{"label":38,"slug":39},"Focus & Scroll Demo","focus-scroll",[41,42,44],"h2",{"id":43},"default-on-invalid-submit","Default on invalid submit",[17,46,47,49],{},[27,48,29],{}," pulls focus to the first invalid field on failed submission:",[51,52,57],"pre",{"className":53,"code":54,"language":55,"meta":56,"style":56},"language-ts shiki shiki-themes github-light github-dark","const onSubmit = form.handleSubmit(async (values) => {\n  await api.send(values)\n})\n","ts","",[27,58,59,104,119],{"__ignoreMap":56},[60,61,64,68,72,75,79,82,85,88,91,95,98,101],"span",{"class":62,"line":63},"line",1,[60,65,67],{"class":66},"szBVR","const",[60,69,71],{"class":70},"sj4cs"," onSubmit",[60,73,74],{"class":66}," =",[60,76,78],{"class":77},"sVt8B"," form.",[60,80,29],{"class":81},"sScJk",[60,83,84],{"class":77},"(",[60,86,87],{"class":66},"async",[60,89,90],{"class":77}," (",[60,92,94],{"class":93},"s4XuR","values",[60,96,97],{"class":77},") ",[60,99,100],{"class":66},"=>",[60,102,103],{"class":77}," {\n",[60,105,107,110,113,116],{"class":62,"line":106},2,[60,108,109],{"class":66},"  await",[60,111,112],{"class":77}," api.",[60,114,115],{"class":81},"send",[60,117,118],{"class":77},"(values)\n",[60,120,122],{"class":62,"line":121},3,[60,123,124],{"class":77},"})\n",[17,126,127],{},"When validation fails, the handler:",[129,130,131,147,150,157],"ol",{},[132,133,134,135,138,139,142,143,146],"li",{},"Increments ",[27,136,137],{},"form.meta.submissionAttempts",". ",[27,140,141],{},"form.meta.submitted"," stays ",[27,144,145],{},"false","; it only flips on a successful callback.",[132,148,149],{},"Surfaces errors at every invalid path.",[132,151,152,153,156],{},"Calls ",[27,154,155],{},"form.focusFirstError()"," (the same method exposed below).",[132,158,152,159,162],{},[27,160,161],{},"onError(errors)"," if you passed one.",[17,164,165],{},"The \"first\" invalid field is in schema-declaration order, which matches the visual reading order for most forms (top to bottom, left to right).",[41,167,169],{"id":168},"focusfirsterroroptions",[27,170,171],{},"focusFirstError(options?)",[51,173,175],{"className":53,"code":174,"language":55,"meta":56,"style":56},"form.focusFirstError({ preventScroll: false })\n",[27,176,177],{"__ignoreMap":56},[60,178,179,182,185,188,190],{"class":62,"line":63},[60,180,181],{"class":77},"form.",[60,183,184],{"class":81},"focusFirstError",[60,186,187],{"class":77},"({ preventScroll: ",[60,189,145],{"class":70},[60,191,192],{"class":77}," })\n",[17,194,195,196,199,200,202,203,206],{},"Returns ",[27,197,198],{},"true"," when a target was found and focused, ",[27,201,145],{}," when no field is in an error state. The optional ",[27,204,205],{},"preventScroll: true"," skips the browser's default focus-related scroll if you've got a custom scroll strategy.",[17,208,209],{},"Reach for this when:",[211,212,213,216,222],"ul",{},[132,214,215],{},"A page-level error banner has a \"Jump to first error\" button.",[132,217,218,219,221],{},"A multi-step form's \"Next\" button should pull focus on validation failure without going through ",[27,220,29],{},".",[132,223,224,225,230],{},"Replacing the default invalid-submit policy with custom UX (see ",[226,227,229],"a",{"href":228},"#customizing-the-invalid-submit-policy","Customizing the invalid-submit policy",").",[41,232,234],{"id":233},"scrolltofirsterroroptions",[27,235,236],{},"scrollToFirstError(options?)",[51,238,240],{"className":53,"code":239,"language":55,"meta":56,"style":56},"form.scrollToFirstError({ behavior: 'smooth', block: 'center' })\n",[27,241,242],{"__ignoreMap":56},[60,243,244,246,249,252,256,259,262],{"class":62,"line":63},[60,245,181],{"class":77},[60,247,248],{"class":81},"scrollToFirstError",[60,250,251],{"class":77},"({ behavior: ",[60,253,255],{"class":254},"sZZnC","'smooth'",[60,257,258],{"class":77},", block: ",[60,260,261],{"class":254},"'center'",[60,263,192],{"class":77},[17,265,195,266,268,269,272,273,276,277,280],{},[27,267,198],{}," when a target was found and scrolled into view. Options forward to the underlying ",[27,270,271],{},"Element.scrollIntoView",": ",[27,274,275],{},"behavior: 'smooth'"," for animated scroll, ",[27,278,279],{},"block: 'center'"," to position the field in the middle of the viewport.",[17,282,283],{},"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:",[51,285,287],{"className":53,"code":286,"language":55,"meta":56,"style":56},"const onSubmit = form.handleSubmit(\n  async (values) => {\n    \u002F* ... *\u002F\n  },\n  () => {\n    form.scrollToFirstError({ behavior: 'smooth', block: 'center' })\n  }\n)\n",[27,288,289,304,319,325,331,341,359,365],{"__ignoreMap":56},[60,290,291,293,295,297,299,301],{"class":62,"line":63},[60,292,67],{"class":66},[60,294,71],{"class":70},[60,296,74],{"class":66},[60,298,78],{"class":77},[60,300,29],{"class":81},[60,302,303],{"class":77},"(\n",[60,305,306,309,311,313,315,317],{"class":62,"line":106},[60,307,308],{"class":66},"  async",[60,310,90],{"class":77},[60,312,94],{"class":93},[60,314,97],{"class":77},[60,316,100],{"class":66},[60,318,103],{"class":77},[60,320,321],{"class":62,"line":121},[60,322,324],{"class":323},"sJ8bj","    \u002F* ... *\u002F\n",[60,326,328],{"class":62,"line":327},4,[60,329,330],{"class":77},"  },\n",[60,332,334,337,339],{"class":62,"line":333},5,[60,335,336],{"class":77},"  () ",[60,338,100],{"class":66},[60,340,103],{"class":77},[60,342,344,347,349,351,353,355,357],{"class":62,"line":343},6,[60,345,346],{"class":77},"    form.",[60,348,248],{"class":81},[60,350,251],{"class":77},[60,352,255],{"class":254},[60,354,258],{"class":77},[60,356,261],{"class":254},[60,358,192],{"class":77},[60,360,362],{"class":62,"line":361},7,[60,363,364],{"class":77},"  }\n",[60,366,368],{"class":62,"line":367},8,[60,369,370],{"class":77},")\n",[41,372,229],{"id":373},"customizing-the-invalid-submit-policy",[17,375,376],{},"Disable the default focus pull at the form level and run your own:",[51,378,380],{"className":53,"code":379,"language":55,"meta":56,"style":56},"useForm({\n  schema,\n  onInvalidSubmit: 'none', \u002F\u002F skip the default focus\n})\n",[27,381,382,390,395,409],{"__ignoreMap":56},[60,383,384,387],{"class":62,"line":63},[60,385,386],{"class":81},"useForm",[60,388,389],{"class":77},"({\n",[60,391,392],{"class":62,"line":106},[60,393,394],{"class":77},"  schema,\n",[60,396,397,400,403,406],{"class":62,"line":121},[60,398,399],{"class":77},"  onInvalidSubmit: ",[60,401,402],{"class":254},"'none'",[60,404,405],{"class":77},", ",[60,407,408],{"class":323},"\u002F\u002F skip the default focus\n",[60,410,411],{"class":62,"line":327},[60,412,124],{"class":77},[17,414,415,416,419],{},"Then drive focus + scroll from ",[27,417,418],{},"onError",":",[51,421,423],{"className":53,"code":422,"language":55,"meta":56,"style":56},"const onSubmit = form.handleSubmit(onSubmitValid, () => {\n  form.scrollToFirstError({ behavior: 'smooth', block: 'center' })\n  form.focusFirstError({ preventScroll: true })\n})\n",[27,424,425,444,461,473],{"__ignoreMap":56},[60,426,427,429,431,433,435,437,440,442],{"class":62,"line":63},[60,428,67],{"class":66},[60,430,71],{"class":70},[60,432,74],{"class":66},[60,434,78],{"class":77},[60,436,29],{"class":81},[60,438,439],{"class":77},"(onSubmitValid, () ",[60,441,100],{"class":66},[60,443,103],{"class":77},[60,445,446,449,451,453,455,457,459],{"class":62,"line":106},[60,447,448],{"class":77},"  form.",[60,450,248],{"class":81},[60,452,251],{"class":77},[60,454,255],{"class":254},[60,456,258],{"class":77},[60,458,261],{"class":254},[60,460,192],{"class":77},[60,462,463,465,467,469,471],{"class":62,"line":121},[60,464,448],{"class":77},[60,466,184],{"class":81},[60,468,187],{"class":77},[60,470,198],{"class":70},[60,472,192],{"class":77},[60,474,475],{"class":62,"line":327},[60,476,124],{"class":77},[17,478,479,480,482,483,405,486,489,490,492,493,496,497,501],{},"The ",[27,481,33],{}," (default), ",[27,484,485],{},"'scroll-to-first-error'",[27,487,488],{},"'both'",", and ",[27,491,402],{}," policy options live on the form config and on ",[27,494,495],{},"createAttaform({ defaults })"," for an app-wide default. See the ",[226,498,500],{"href":499},"\u002Fdocs\u002Freference\u002Ftypes","Types reference"," for the full set.",[41,503,505],{"id":504},"where-to-next","Where to next",[211,507,508,516,523],{},[132,509,510,515],{},[226,511,513],{"href":512},"\u002Fdocs\u002Fsubmitting\u002Fhandle-submit",[27,514,29],{},": the dispatch surface that calls these by default.",[132,517,518,522],{},[226,519,521],{"href":520},"\u002Fdocs\u002Fsubmitting\u002Fserver-side-errors","Server-side errors",": bring API failures back into the same focus \u002F scroll machinery.",[132,524,525,529],{},[226,526,528],{"href":527},"\u002Fdocs\u002Fvalidation\u002Fshowing-errors","Display state and showing errors",": the predicate that decides when errors render.",[531,532,533],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":56,"searchDepth":106,"depth":106,"links":535},[536,537,538,539,540],{"id":43,"depth":106,"text":44},{"id":168,"depth":106,"text":171},{"id":233,"depth":106,"text":236},{"id":373,"depth":106,"text":229},{"id":504,"depth":106,"text":505},"handleSubmit pulls focus to the first invalid field by default. focusFirstError and scrollToFirstError are the imperative escape hatches when the default isn't enough.","md",{},[545,548,551,554],{"label":546,"value":547},"Category","Return methods",{"label":549,"value":550,"kind":27},"Auto behavior","handleSubmit on invalid → focusFirstError",{"label":552,"value":553,"kind":27},"Helpers","focusFirstError(options?) · scrollToFirstError(options?)",{"label":555,"value":556,"kind":27},"Returns","boolean; true if a target was found",true,"\u002Fdocs\u002Fsubmitting\u002Ffocus-scroll",{"title":5,"description":541},null,"docs\u002Fsubmitting\u002Ffocus-scroll","DO0PNcHVLjH-WSdlrJ4jZTZU5yDyncNA6aDjNfBOTPI",1780949760780]