[{"data":1,"prerenderedAt":896},["ShallowReactive",2],{"content-\u002Fdocs\u002Freading-the-form\u002Ferrors":3},{"id":4,"title":5,"body":6,"description":878,"extension":879,"meta":880,"metaRows":881,"navigation":150,"path":891,"seo":892,"source":893,"stem":894,"__hash__":895},"docs\u002Fdocs\u002Freading-the-form\u002Ferrors.md","errors",{"type":7,"value":8,"toc":869},"minimark",[9,15,22,25,47,51,56,210,249,256,325,350,354,359,414,450,458,464,567,588,614,632,643,678,689,693,707,761,791,795,865],[10,11,12],"h1",{"id":5},[13,14,5],"code",{},[16,17,18],"blockquote",{},[19,20,21],"p",{},"A reactive Proxy keyed by schema paths. Every leaf carries its current error list, container reads aggregate everything underneath, and the whole tree re-renders the moment validation re-runs.",[23,24],"docs-meta-table",{},[19,26,27,30,31,34,35,38,39,42,43,46],{},[13,28,29],{},"form.errors"," is the raw validation surface, paired one-to-one with ",[13,32,33],{},"form.values"," and ",[13,36,37],{},"form.fields",". The demo seeds invalid values up front so the panels light up on mount, then updates live as you edit each field. The container panel shows the live ",[13,40,41],{},"profile"," sub-tree, the form-level panel shows the root-only errors via ",[13,44,45],{},"form.errors([])",", and the whole-form panel shows the full sparse tree.",[48,49],"docs-demo",{"label":50,"slug":5},"form.errors Demo",[52,53,55],"h2",{"id":54},"leaf-reads","Leaf reads",[57,58,63],"pre",{"className":59,"code":60,"language":61,"meta":62,"style":62},"language-ts shiki shiki-themes github-light github-dark","const schema = z.object({\n  email: z.email('Enter a valid email'),\n  name: z.string().min(1, 'Name is required'),\n})\n\nconst form = useForm({ schema })\n\nform.errors.email \u002F\u002F readonly ValidationError[]\nform.errors.email[0]?.message \u002F\u002F 'Enter a valid email' | undefined\nform.errors.email.length \u002F\u002F 0 when valid\n","ts","",[13,64,65,92,111,139,145,152,168,173,183,198],{"__ignoreMap":62},[66,67,70,74,78,81,85,89],"span",{"class":68,"line":69},"line",1,[66,71,73],{"class":72},"szBVR","const",[66,75,77],{"class":76},"sj4cs"," schema",[66,79,80],{"class":72}," =",[66,82,84],{"class":83},"sVt8B"," z.",[66,86,88],{"class":87},"sScJk","object",[66,90,91],{"class":83},"({\n",[66,93,95,98,101,104,108],{"class":68,"line":94},2,[66,96,97],{"class":83},"  email: z.",[66,99,100],{"class":87},"email",[66,102,103],{"class":83},"(",[66,105,107],{"class":106},"sZZnC","'Enter a valid email'",[66,109,110],{"class":83},"),\n",[66,112,114,117,120,123,126,128,131,134,137],{"class":68,"line":113},3,[66,115,116],{"class":83},"  name: z.",[66,118,119],{"class":87},"string",[66,121,122],{"class":83},"().",[66,124,125],{"class":87},"min",[66,127,103],{"class":83},[66,129,130],{"class":76},"1",[66,132,133],{"class":83},", ",[66,135,136],{"class":106},"'Name is required'",[66,138,110],{"class":83},[66,140,142],{"class":68,"line":141},4,[66,143,144],{"class":83},"})\n",[66,146,148],{"class":68,"line":147},5,[66,149,151],{"emptyLinePlaceholder":150},true,"\n",[66,153,155,157,160,162,165],{"class":68,"line":154},6,[66,156,73],{"class":72},[66,158,159],{"class":76}," form",[66,161,80],{"class":72},[66,163,164],{"class":87}," useForm",[66,166,167],{"class":83},"({ schema })\n",[66,169,171],{"class":68,"line":170},7,[66,172,151],{"emptyLinePlaceholder":150},[66,174,176,179],{"class":68,"line":175},8,[66,177,178],{"class":83},"form.errors.email ",[66,180,182],{"class":181},"sJ8bj","\u002F\u002F readonly ValidationError[]\n",[66,184,186,189,192,195],{"class":68,"line":185},9,[66,187,188],{"class":83},"form.errors.email[",[66,190,191],{"class":76},"0",[66,193,194],{"class":83},"]?.message ",[66,196,197],{"class":181},"\u002F\u002F 'Enter a valid email' | undefined\n",[66,199,201,204,207],{"class":68,"line":200},10,[66,202,203],{"class":83},"form.errors.email.",[66,205,206],{"class":76},"length",[66,208,209],{"class":181}," \u002F\u002F 0 when valid\n",[19,211,212,213,216,217,220,221,224,225,228,229,231,232,235,236,244,245,248],{},"A static object leaf always returns an array, ",[13,214,215],{},"readonly ValidationError[]",", empty when valid. Reads past a dynamic boundary carry ",[13,218,219],{},"| undefined",", because the node there may be absent: a numeric array index (",[13,222,223],{},"form.errors.todos[3]?.title","), a record key (",[13,226,227],{},"form.errors.byId.missing","), or a field that only exists on an inactive discriminated-union variant. That ",[13,230,219],{}," is honest. Dot and index access is pure navigation, so a key the schema doesn't declare and the data doesn't hold reads ",[13,233,234],{},"undefined",", never a stand-in proxy, and a truthy check agrees with the runtime. The one exception is a server error parked at a non-schema key: the error stores count as holding it, so it stays reachable and its message lands on the ",[237,238,240,243],"a",{"href":239},"#the-sentinel-container-self-errors",[13,241,242],{},"''"," container-self sentinel"," (",[13,246,247],{},"form.errors.ghost['']",").",[19,250,251,252,255],{},"The first error's ",[13,253,254],{},".message"," is what most templates render:",[57,257,261],{"className":258,"code":259,"language":260,"meta":62,"style":62},"language-vue shiki shiki-themes github-light github-dark","\u003Ctemplate>\n  \u003Cinput v-register=\"form.register('email')\" \u002F>\n  \u003Cp v-if=\"form.errors.email.length\">{{ form.errors.email[0]?.message }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n","vue",[13,262,263,275,295,316],{"__ignoreMap":62},[66,264,265,268,272],{"class":68,"line":69},[66,266,267],{"class":83},"\u003C",[66,269,271],{"class":270},"s9eBZ","template",[66,273,274],{"class":83},">\n",[66,276,277,280,283,286,289,292],{"class":68,"line":94},[66,278,279],{"class":83},"  \u003C",[66,281,282],{"class":270},"input",[66,284,285],{"class":87}," v-register",[66,287,288],{"class":83},"=",[66,290,291],{"class":106},"\"form.register('email')\"",[66,293,294],{"class":83}," \u002F>\n",[66,296,297,299,301,304,306,309,312,314],{"class":68,"line":113},[66,298,279],{"class":83},[66,300,19],{"class":270},[66,302,303],{"class":87}," v-if",[66,305,288],{"class":83},[66,307,308],{"class":106},"\"form.errors.email.length\"",[66,310,311],{"class":83},">{{ form.errors.email[0]?.message }}\u003C\u002F",[66,313,19],{"class":270},[66,315,274],{"class":83},[66,317,318,321,323],{"class":68,"line":141},[66,319,320],{"class":83},"\u003C\u002F",[66,322,271],{"class":270},[66,324,274],{"class":83},[19,326,327,328,334,335,341,342,345,346,349],{},"For display ergonomics, gating by ",[237,329,331],{"href":330},"\u002Fdocs\u002Fvalidation\u002Fshowing-errors",[13,332,333],{},"getDisplayState"," and pulling the first error in one shot, reach for ",[237,336,338],{"href":337},"\u002Fdocs\u002Freading-the-form\u002Ffields",[13,339,340],{},"form.fields.email.firstError"," paired with ",[13,343,344],{},"form.fields.email.showErrors",". The errors Proxy is the raw aggregate; the fields Proxy is the same data with display gating and ",[13,347,348],{},"firstError"," sugar layered on.",[52,351,353],{"id":352},"container-reads","Container reads",[19,355,356,358],{},[13,357,29],{}," is a drillable Proxy: dot-access descends into containers (returning a sub-Proxy you can keep drilling), and the call form returns a flat aggregate at any path. The two surfaces serve different jobs:",[57,360,362],{"className":59,"code":361,"language":61,"meta":62,"style":62},"form.errors.profile \u002F\u002F sub-Proxy: { '': [...refines], bio: [...], ... }\nform.errors('profile') \u002F\u002F flat array: every error inside profile + container-self\nform.errors() \u002F\u002F flat array: every error in the form\nform.errors([]) \u002F\u002F flat array: global errors only (root .refine(), form-level setErrors)\n",[13,363,364,372,390,402],{"__ignoreMap":62},[66,365,366,369],{"class":68,"line":69},[66,367,368],{"class":83},"form.errors.profile ",[66,370,371],{"class":181},"\u002F\u002F sub-Proxy: { '': [...refines], bio: [...], ... }\n",[66,373,374,377,379,381,384,387],{"class":68,"line":94},[66,375,376],{"class":83},"form.",[66,378,5],{"class":87},[66,380,103],{"class":83},[66,382,383],{"class":106},"'profile'",[66,385,386],{"class":83},") ",[66,388,389],{"class":181},"\u002F\u002F flat array: every error inside profile + container-self\n",[66,391,392,394,396,399],{"class":68,"line":113},[66,393,376],{"class":83},[66,395,5],{"class":87},[66,397,398],{"class":83},"() ",[66,400,401],{"class":181},"\u002F\u002F flat array: every error in the form\n",[66,403,404,406,408,411],{"class":68,"line":141},[66,405,376],{"class":83},[66,407,5],{"class":87},[66,409,410],{"class":83},"([]) ",[66,412,413],{"class":181},"\u002F\u002F flat array: global errors only (root .refine(), form-level setErrors)\n",[19,415,416,419,420,423,424,426,427,430,431,437,438,441,442,445,446,449],{},[13,417,418],{},"form.errors()"," is the cheapest \"is anything wrong?\" check (",[13,421,422],{},"form.errors().length === 0"," when the form is valid). Its narrower companion ",[13,425,45],{}," returns only the global, form-level errors (a root ",[13,428,429],{},".refine()",", an imperatively-set form error), with no field errors mixed in. For aggregated counts and submission-state bits, see ",[237,432,434],{"href":433},"\u002Fdocs\u002Freading-the-form\u002Fmeta",[13,435,436],{},"form.meta",". When you serialize the dot-form (",[13,439,440],{},"JSON.stringify(form.errors)"," or ",[13,443,444],{},"{{ form.errors }}"," in a template), the Proxy materializes the live sparse tree, so you can dump the whole error state for debugging without losing structure; global errors sit under the ",[13,447,448],{},"'[]'"," key in that dump, kept distinct from every field.",[451,452,454,455,457],"h3",{"id":453},"the-sentinel-container-self-errors","The ",[13,456,242],{}," sentinel: container-self errors",[19,459,460,461,463],{},"A cross-field ",[13,462,429],{}," lives on a container, not a leaf:",[57,465,467],{"className":59,"code":466,"language":61,"meta":62,"style":62},"const schema = z.object({\n  profile: z\n    .object({\n      bio: z.string().max(50),\n      handle: z.string(),\n    })\n    .refine((p) => p.bio.includes(p.handle), 'Bio must mention your handle'),\n})\n",[13,468,469,483,488,497,516,526,531,563],{"__ignoreMap":62},[66,470,471,473,475,477,479,481],{"class":68,"line":69},[66,472,73],{"class":72},[66,474,77],{"class":76},[66,476,80],{"class":72},[66,478,84],{"class":83},[66,480,88],{"class":87},[66,482,91],{"class":83},[66,484,485],{"class":68,"line":94},[66,486,487],{"class":83},"  profile: z\n",[66,489,490,493,495],{"class":68,"line":113},[66,491,492],{"class":83},"    .",[66,494,88],{"class":87},[66,496,91],{"class":83},[66,498,499,502,504,506,509,511,514],{"class":68,"line":141},[66,500,501],{"class":83},"      bio: z.",[66,503,119],{"class":87},[66,505,122],{"class":83},[66,507,508],{"class":87},"max",[66,510,103],{"class":83},[66,512,513],{"class":76},"50",[66,515,110],{"class":83},[66,517,518,521,523],{"class":68,"line":147},[66,519,520],{"class":83},"      handle: z.",[66,522,119],{"class":87},[66,524,525],{"class":83},"(),\n",[66,527,528],{"class":68,"line":154},[66,529,530],{"class":83},"    })\n",[66,532,533,535,538,541,544,546,549,552,555,558,561],{"class":68,"line":170},[66,534,492],{"class":83},[66,536,537],{"class":87},"refine",[66,539,540],{"class":83},"((",[66,542,19],{"class":543},"s4XuR",[66,545,386],{"class":83},[66,547,548],{"class":72},"=>",[66,550,551],{"class":83}," p.bio.",[66,553,554],{"class":87},"includes",[66,556,557],{"class":83},"(p.handle), ",[66,559,560],{"class":106},"'Bio must mention your handle'",[66,562,110],{"class":83},[66,564,565],{"class":68,"line":175},[66,566,144],{"class":83},[19,568,569,570,573,574,577,578,34,581,584,585,587],{},"The refine's error path is ",[13,571,572],{},"['profile']",", the container itself. To keep ",[13,575,576],{},"form.errors.profile"," readable alongside leaf errors at ",[13,579,580],{},"['profile', 'bio']",[13,582,583],{},"['profile', 'handle']",", container-self errors land in the materialized tree under the ",[13,586,242],{}," sentinel slot:",[57,589,591],{"className":59,"code":590,"language":61,"meta":62,"style":62},"form.errors.profile[''] \u002F\u002F refine errors on profile (and any other container-self entries)\nform.errors.profile.bio \u002F\u002F leaf errors on bio\n",[13,592,593,606],{"__ignoreMap":62},[66,594,595,598,600,603],{"class":68,"line":69},[66,596,597],{"class":83},"form.errors.profile[",[66,599,242],{"class":106},[66,601,602],{"class":83},"] ",[66,604,605],{"class":181},"\u002F\u002F refine errors on profile (and any other container-self entries)\n",[66,607,608,611],{"class":68,"line":94},[66,609,610],{"class":83},"form.errors.profile.bio ",[66,612,613],{"class":181},"\u002F\u002F leaf errors on bio\n",[19,615,616,619,620,623,624,627,628,631],{},[13,617,618],{},"JSON.stringify(form.errors.profile)"," materializes as ",[13,621,622],{},"{ '': [refineError], bio: [maxError], handle: [...] }",". Both the refine and the descendant leaves coexist; nothing clobbers anything. The same convention reaches all the way down: a refine on ",[13,625,626],{},"profile.address"," lands at ",[13,629,630],{},"form.errors.profile.address['']",".",[19,633,634,635,638,639,642],{},"The call form is the flat alternative: ",[13,636,637],{},"form.errors('profile')"," returns one ",[13,640,641],{},"ValidationError[]"," containing the refine PLUS every descendant leaf error in declaration order, no structure. Reach for the structural tree when you want to render per-field; reach for the call form when you want \"anything wrong under this container?\".",[19,644,645,646,648,649,651,652,655,656,658,659,661,662,665,666,668,669,671,672,674,675,677],{},"The sentinel is a nested-container convention: it appears one level deep or more. The root has no ",[13,647,242],{}," self-slot, because at the root ",[13,650,242],{}," is just an ordinary field key. ",[13,653,654],{},"form.errors['']"," reads a literal field named ",[13,657,242],{}," (a rare but valid schema key), while global, form-level errors (a root ",[13,660,429],{},", a path-less ",[13,663,664],{},"setErrors"," call) live at the structurally-distinct root path and read through ",[13,667,45],{},". In a ",[13,670,440],{}," dump they appear under the ",[13,673,448],{}," key, never under ",[13,676,242],{},", so the two never collide.",[19,679,680,681,683,684,686,687,631],{},"If your schema legitimately declares a field literally named ",[13,682,242],{}," at a nested container (an exceptionally rare choice), that leaf's own errors and the container-self errors share the sentinel slot, and both arrays concatenate into a single read. At the root no such collision exists: ",[13,685,242],{}," is a plain field there, kept entirely separate from the global bucket at ",[13,688,45],{},[52,690,692],{"id":691},"setting-errors-imperatively","Setting errors imperatively",[19,694,695,696,699,700,702,703,706],{},"Server-side errors land in the same reactive store as Zod errors. Hand ",[13,697,698],{},"form.setErrors"," a ",[13,701,641],{}," and each entry lands at its ",[13,704,705],{},"path",":",[57,708,710],{"className":59,"code":709,"language":61,"meta":62,"style":62},"form.setErrors([\n  { path: ['email'], message: 'Already taken' },\n  { path: ['profile', 'handle'], message: 'Reserved' },\n])\n",[13,711,712,721,738,756],{"__ignoreMap":62},[66,713,714,716,718],{"class":68,"line":69},[66,715,376],{"class":83},[66,717,664],{"class":87},[66,719,720],{"class":83},"([\n",[66,722,723,726,729,732,735],{"class":68,"line":94},[66,724,725],{"class":83},"  { path: [",[66,727,728],{"class":106},"'email'",[66,730,731],{"class":83},"], message: ",[66,733,734],{"class":106},"'Already taken'",[66,736,737],{"class":83}," },\n",[66,739,740,742,744,746,749,751,754],{"class":68,"line":113},[66,741,725],{"class":83},[66,743,383],{"class":106},[66,745,133],{"class":83},[66,747,748],{"class":106},"'handle'",[66,750,731],{"class":83},[66,752,753],{"class":106},"'Reserved'",[66,755,737],{"class":83},[66,757,758],{"class":68,"line":141},[66,759,760],{"class":83},"])\n",[19,762,763,34,766,769,770,773,774,777,778,782,783,786,787,790],{},[13,764,765],{},"form.errors.email",[13,767,768],{},"form.errors.profile.handle"," update immediately, and any ",[13,771,772],{},"form.fields.\u003Cpath>.firstError"," \u002F ",[13,775,776],{},"form.fields.\u003Cpath>.showErrors"," reads update with them. The render surface is identical whether the error came from Zod or your API. See ",[237,779,781],{"href":780},"\u002Fdocs\u002Fsubmitting\u002Fserver-side-errors","Server-side errors"," for the full ",[13,784,785],{},"handleSubmit"," flow: scoped and functional updates, clearing, and the opaque ",[13,788,789],{},"data"," payload slot.",[52,792,794],{"id":793},"where-to-next","Where to next",[796,797,798,806,815,829,847,854],"ul",{},[799,800,801,805],"li",{},[237,802,804],{"href":803},"\u002Fdocs\u002Freading-the-form\u002Fthe-form","The form",": every other reactive read.",[799,807,808,814],{},[237,809,811],{"href":810},"\u002Fdocs\u002Freading-the-form\u002Fvalues",[13,812,813],{},"values",": the read companion to errors.",[799,816,817,822,823,773,825,828],{},[237,818,819],{"href":337},[13,820,821],{},"fields",": per-leaf state, including the gated ",[13,824,348],{},[13,826,827],{},"showErrors"," pairing.",[799,830,831,836,837,133,840,133,843,846],{},[237,832,833],{"href":433},[13,834,835],{},"meta",": the form-level aggregates (",[13,838,839],{},"errorCount",[13,841,842],{},"valid",[13,844,845],{},"submitting",", etc.).",[799,848,849,853],{},[237,850,852],{"href":851},"\u002Fdocs\u002Fvalidation\u002Fwhen-validation-runs","When validation runs",": the moment errors appear.",[799,855,856,858,859,34,861,864],{},[237,857,781],{"href":780},": ",[13,860,664],{},[13,862,863],{},"clearErrors"," in full.",[866,867,868],"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 .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}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":62,"searchDepth":94,"depth":94,"links":870},[871,872,876,877],{"id":54,"depth":94,"text":55},{"id":352,"depth":94,"text":353,"children":873},[874],{"id":453,"depth":113,"text":875},"The '' sentinel: container-self errors",{"id":691,"depth":94,"text":692},{"id":793,"depth":94,"text":794},"form.errors is a reactive Proxy keyed by schema paths. Read any leaf's error list, with errors[0]?.message ready to render and the full array available for richer surfaces.","md",{},[882,885,888],{"label":883,"value":884},"Category","Return property",{"label":886,"value":887,"kind":13},"Type","ErrorsProxyShape\u003CForm>",{"label":889,"value":890},"Reactive","Yes","\u002Fdocs\u002Freading-the-form\u002Ferrors",{"title":5,"description":878},null,"docs\u002Freading-the-form\u002Ferrors","tTxSjCESDtFsziziJVfFf2CbotrLk4M-ika_fNbwSr4",1781745871825]