[{"data":1,"prerenderedAt":1151},["ShallowReactive",2],{"navigation":3,"$fMW9jOOkNpZriWVK3P_bZYNOyYR2IEPvrHsC7cDBlJJc":405,"/docs/forms/tanstack-form":418,"surround-/docs/forms/tanstack-form":1145},[4],{"title":5,"path":6,"stem":7,"children":8,"page":17},"Docs","/docs","docs",[9,90,353,360,366,379],{"path":6,"stem":7,"title":10,"type":11,"children":12},"Get Started","group",[13,19,44,48,52,70,74,78,82,86],{"title":14,"path":15,"stem":16,"new":17,"type":18,"children":-1},"Introduction","/docs/introduction","docs/01.introduction",false,"page",{"title":20,"path":21,"stem":22,"children":23,"new":17,"type":11},"Installation","/docs/installation","docs/02.installation",[24,28,32,36,40],{"title":25,"path":26,"stem":27,"new":17,"type":18,"children":-1},"Vite","/docs/installation/vite","docs/installation/01.vite",{"title":29,"path":30,"stem":31,"new":17,"type":18,"children":-1},"Nuxt","/docs/installation/nuxt","docs/installation/02.nuxt",{"title":33,"path":34,"stem":35,"new":17,"type":18,"children":-1},"Astro","/docs/installation/astro","docs/installation/03.astro",{"title":37,"path":38,"stem":39,"new":17,"type":18,"children":-1},"Laravel","/docs/installation/laravel","docs/installation/04.laravel",{"title":41,"path":42,"stem":43,"new":17,"type":18,"children":-1},"Manual Installation","/docs/installation/manual","docs/installation/05.manual",{"title":45,"path":46,"stem":47,"new":17,"type":18,"children":-1},"components.json","/docs/components-json","docs/03.components-json",{"title":49,"path":50,"stem":51,"new":17,"type":18,"children":-1},"Theming","/docs/theming","docs/04.theming",{"title":53,"path":54,"stem":55,"children":56,"new":17,"type":11},"Dark Mode","/docs/dark-mode","docs/05.dark-mode",[57,60,63,67],{"title":25,"path":58,"stem":59,"new":17,"type":18,"children":-1},"/docs/dark-mode/vite","docs/dark-mode/01.vite",{"title":29,"path":61,"stem":62,"new":17,"type":18,"children":-1},"/docs/dark-mode/nuxt","docs/dark-mode/02.nuxt",{"title":64,"path":65,"stem":66,"new":17,"type":18,"children":-1},"Vitepress","/docs/dark-mode/vitepress","docs/dark-mode/03.vitepress",{"title":33,"path":68,"stem":69,"new":17,"type":18,"children":-1},"/docs/dark-mode/astro","docs/dark-mode/04.astro",{"title":71,"path":72,"stem":73,"new":17,"type":18,"children":-1},"CLI","/docs/cli","docs/06.cli",{"title":75,"path":76,"stem":77,"new":17,"type":18,"children":-1},"JavaScript","/docs/javascript","docs/07.javascript",{"title":79,"path":80,"stem":81,"new":17,"type":18,"children":-1},"Figma","/docs/figma","docs/09.figma",{"title":83,"path":84,"stem":85,"new":17,"type":18,"children":-1},"Changelog","/docs/changelog","docs/10.changelog",{"title":87,"path":88,"stem":89,"new":17,"type":18,"children":-1},"Legacy Docs","/docs/legacy","docs/11.legacy",{"title":91,"path":92,"stem":93,"children":94,"new":17,"type":11},"Components","/docs/components","docs/02.components",[95,100,104,108,112,116,120,124,128,133,137,141,145,149,153,157,161,165,169,173,177,181,185,189,193,197,201,205,209,213,217,221,225,229,233,237,241,245,249,253,257,261,265,269,273,277,281,285,289,293,297,301,305,309,313,317,321,325,329,333,337,341,345,349],{"title":96,"path":97,"stem":98,"new":17,"type":99,"children":-1},"Accordion","/docs/components/accordion","docs/components/accordion","component",{"title":101,"path":102,"stem":103,"new":17,"type":99,"children":-1},"Alert","/docs/components/alert","docs/components/alert",{"title":105,"path":106,"stem":107,"new":17,"type":99,"children":-1},"Alert Dialog","/docs/components/alert-dialog","docs/components/alert-dialog",{"title":109,"path":110,"stem":111,"new":17,"type":99,"children":-1},"Aspect Ratio","/docs/components/aspect-ratio","docs/components/aspect-ratio",{"title":113,"path":114,"stem":115,"new":17,"type":99,"children":-1},"Avatar","/docs/components/avatar","docs/components/avatar",{"title":117,"path":118,"stem":119,"new":17,"type":99,"children":-1},"Badge","/docs/components/badge","docs/components/badge",{"title":121,"path":122,"stem":123,"new":17,"type":99,"children":-1},"Breadcrumb","/docs/components/breadcrumb","docs/components/breadcrumb",{"title":125,"path":126,"stem":127,"new":17,"type":99,"children":-1},"Button","/docs/components/button","docs/components/button",{"title":129,"path":130,"stem":131,"new":132,"type":99,"children":-1},"Button Group","/docs/components/button-group","docs/components/button-group",true,{"title":134,"path":135,"stem":136,"new":17,"type":99,"children":-1},"Calendar","/docs/components/calendar","docs/components/calendar",{"title":138,"path":139,"stem":140,"new":17,"type":99,"children":-1},"Card","/docs/components/card","docs/components/card",{"title":142,"path":143,"stem":144,"new":17,"type":99,"children":-1},"Carousel","/docs/components/carousel","docs/components/carousel",{"title":146,"path":147,"stem":148,"new":17,"type":99,"children":-1},"Chart","/docs/components/chart","docs/components/chart",{"title":150,"path":151,"stem":152,"new":17,"type":99,"children":-1},"Checkbox","/docs/components/checkbox","docs/components/checkbox",{"title":154,"path":155,"stem":156,"new":17,"type":99,"children":-1},"Collapsible","/docs/components/collapsible","docs/components/collapsible",{"title":158,"path":159,"stem":160,"new":17,"type":99,"children":-1},"Combobox","/docs/components/combobox","docs/components/combobox",{"title":162,"path":163,"stem":164,"new":17,"type":99,"children":-1},"Command","/docs/components/command","docs/components/command",{"title":166,"path":167,"stem":168,"new":17,"type":99,"children":-1},"Context Menu","/docs/components/context-menu","docs/components/context-menu",{"title":170,"path":171,"stem":172,"new":17,"type":99,"children":-1},"Data Table","/docs/components/data-table","docs/components/data-table",{"title":174,"path":175,"stem":176,"new":17,"type":99,"children":-1},"Date Picker","/docs/components/date-picker","docs/components/date-picker",{"title":178,"path":179,"stem":180,"new":17,"type":99,"children":-1},"Dialog","/docs/components/dialog","docs/components/dialog",{"title":182,"path":183,"stem":184,"new":17,"type":99,"children":-1},"Drawer","/docs/components/drawer","docs/components/drawer",{"title":186,"path":187,"stem":188,"new":17,"type":99,"children":-1},"Dropdown Menu","/docs/components/dropdown-menu","docs/components/dropdown-menu",{"title":190,"path":191,"stem":192,"new":132,"type":99,"children":-1},"Empty","/docs/components/empty","docs/components/empty",{"title":194,"path":195,"stem":196,"new":132,"type":99,"children":-1},"Field","/docs/components/field","docs/components/field",{"title":198,"path":199,"stem":200,"new":17,"type":99,"children":-1},"Form","/docs/components/form","docs/components/form",{"title":202,"path":203,"stem":204,"new":17,"type":99,"children":-1},"Hover Card","/docs/components/hover-card","docs/components/hover-card",{"title":206,"path":207,"stem":208,"new":17,"type":99,"children":-1},"Input","/docs/components/input","docs/components/input",{"title":210,"path":211,"stem":212,"new":132,"type":99,"children":-1},"Input Group","/docs/components/input-group","docs/components/input-group",{"title":214,"path":215,"stem":216,"new":17,"type":99,"children":-1},"Input OTP","/docs/components/input-otp","docs/components/input-otp",{"title":218,"path":219,"stem":220,"new":132,"type":99,"children":-1},"Item","/docs/components/item","docs/components/item",{"title":222,"path":223,"stem":224,"new":132,"type":99,"children":-1},"Kbd","/docs/components/kbd","docs/components/kbd",{"title":226,"path":227,"stem":228,"new":17,"type":99,"children":-1},"Label","/docs/components/label","docs/components/label",{"title":230,"path":231,"stem":232,"new":17,"type":99,"children":-1},"Menubar","/docs/components/menubar","docs/components/menubar",{"title":234,"path":235,"stem":236,"new":132,"type":99,"children":-1},"Native Select","/docs/components/native-select","docs/components/native-select",{"title":238,"path":239,"stem":240,"new":17,"type":99,"children":-1},"Navigation Menu","/docs/components/navigation-menu","docs/components/navigation-menu",{"title":242,"path":243,"stem":244,"new":17,"type":99,"children":-1},"Number Field","/docs/components/number-field","docs/components/number-field",{"title":246,"path":247,"stem":248,"new":17,"type":99,"children":-1},"Pagination","/docs/components/pagination","docs/components/pagination",{"title":250,"path":251,"stem":252,"new":17,"type":99,"children":-1},"Pin Input","/docs/components/pin-input","docs/components/pin-input",{"title":254,"path":255,"stem":256,"new":17,"type":99,"children":-1},"Popover","/docs/components/popover","docs/components/popover",{"title":258,"path":259,"stem":260,"new":17,"type":99,"children":-1},"Progress","/docs/components/progress","docs/components/progress",{"title":262,"path":263,"stem":264,"new":17,"type":99,"children":-1},"Radio Group","/docs/components/radio-group","docs/components/radio-group",{"title":266,"path":267,"stem":268,"new":17,"type":99,"children":-1},"Range Calendar","/docs/components/range-calendar","docs/components/range-calendar",{"title":270,"path":271,"stem":272,"new":17,"type":99,"children":-1},"Resizable","/docs/components/resizable","docs/components/resizable",{"title":274,"path":275,"stem":276,"new":17,"type":99,"children":-1},"Scroll Area","/docs/components/scroll-area","docs/components/scroll-area",{"title":278,"path":279,"stem":280,"new":17,"type":99,"children":-1},"Select","/docs/components/select","docs/components/select",{"title":282,"path":283,"stem":284,"new":17,"type":99,"children":-1},"Separator","/docs/components/separator","docs/components/separator",{"title":286,"path":287,"stem":288,"new":17,"type":99,"children":-1},"Sheet","/docs/components/sheet","docs/components/sheet",{"title":290,"path":291,"stem":292,"new":17,"type":99,"children":-1},"Sidebar","/docs/components/sidebar","docs/components/sidebar",{"title":294,"path":295,"stem":296,"new":17,"type":99,"children":-1},"Skeleton","/docs/components/skeleton","docs/components/skeleton",{"title":298,"path":299,"stem":300,"new":17,"type":99,"children":-1},"Slider","/docs/components/slider","docs/components/slider",{"title":302,"path":303,"stem":304,"new":17,"type":99,"children":-1},"Sonner","/docs/components/sonner","docs/components/sonner",{"title":306,"path":307,"stem":308,"new":132,"type":99,"children":-1},"Spinner","/docs/components/spinner","docs/components/spinner",{"title":310,"path":311,"stem":312,"new":17,"type":99,"children":-1},"Stepper","/docs/components/stepper","docs/components/stepper",{"title":314,"path":315,"stem":316,"new":17,"type":99,"children":-1},"Switch","/docs/components/switch","docs/components/switch",{"title":318,"path":319,"stem":320,"new":17,"type":99,"children":-1},"Table","/docs/components/table","docs/components/table",{"title":322,"path":323,"stem":324,"new":17,"type":99,"children":-1},"Tabs","/docs/components/tabs","docs/components/tabs",{"title":326,"path":327,"stem":328,"new":17,"type":99,"children":-1},"Tags Input","/docs/components/tags-input","docs/components/tags-input",{"title":330,"path":331,"stem":332,"new":17,"type":99,"children":-1},"Textarea","/docs/components/textarea","docs/components/textarea",{"title":334,"path":335,"stem":336,"new":17,"type":99,"children":-1},"Toast","/docs/components/toast","docs/components/toast",{"title":338,"path":339,"stem":340,"new":17,"type":99,"children":-1},"Toggle","/docs/components/toggle","docs/components/toggle",{"title":342,"path":343,"stem":344,"new":17,"type":99,"children":-1},"Toggle Group","/docs/components/toggle-group","docs/components/toggle-group",{"title":346,"path":347,"stem":348,"new":17,"type":99,"children":-1},"Tooltip","/docs/components/tooltip","docs/components/tooltip",{"title":350,"path":351,"stem":352,"new":17,"type":99,"children":-1},"Typography","/docs/components/typography","docs/components/typography",{"title":20,"path":21,"stem":22,"children":354,"new":17,"type":11},[355,356,357,358,359],{"title":25,"path":26,"stem":27,"new":17,"type":18,"children":-1},{"title":29,"path":30,"stem":31,"new":17,"type":18,"children":-1},{"title":33,"path":34,"stem":35,"new":17,"type":18,"children":-1},{"title":37,"path":38,"stem":39,"new":17,"type":18,"children":-1},{"title":41,"path":42,"stem":43,"new":17,"type":18,"children":-1},{"title":53,"path":54,"stem":55,"children":361,"new":17,"type":11},[362,363,364,365],{"title":25,"path":58,"stem":59,"new":17,"type":18,"children":-1},{"title":29,"path":61,"stem":62,"new":17,"type":18,"children":-1},{"title":64,"path":65,"stem":66,"new":17,"type":18,"children":-1},{"title":33,"path":68,"stem":69,"new":17,"type":18,"children":-1},{"title":367,"path":368,"stem":369,"children":370,"new":17,"type":11},"Forms","/docs/forms","docs/forms",[371,375],{"title":372,"path":373,"stem":374,"new":17,"type":18,"children":-1},"VeeValidate","/docs/forms/vee-validate","docs/forms/01.vee-validate",{"title":376,"path":377,"stem":378,"new":17,"type":18,"children":-1},"TanStack Form","/docs/forms/tanstack-form","docs/forms/02.tanstack-form",{"title":380,"path":381,"stem":382,"children":383,"new":17,"type":11},"Registry","/docs/registry","docs/registry/index",[384,385,389,393,397,401],{"title":380,"path":381,"stem":382,"new":17,"type":18,"children":-1},{"title":386,"path":387,"stem":388,"new":17,"type":18,"children":-1},"Examples","/docs/registry/examples","docs/registry/examples",{"title":390,"path":391,"stem":392,"new":17,"type":18,"children":-1},"FAQ","/docs/registry/faq","docs/registry/faq",{"title":394,"path":395,"stem":396,"new":17,"type":18,"children":-1},"Getting Started","/docs/registry/getting-started","docs/registry/getting-started",{"title":398,"path":399,"stem":400,"new":17,"type":18,"children":-1},"registry-item.json","/docs/registry/registry-item-json","docs/registry/registry-item-json",{"title":402,"path":403,"stem":404,"new":17,"type":18,"children":-1},"registry.json","/docs/registry/registry-json","docs/registry/registry-json",{"repo":406},{"id":407,"name":408,"repo":409,"description":410,"createdAt":411,"updatedAt":412,"pushedAt":413,"stars":414,"watchers":415,"forks":416,"defaultBranch":417},658791894,"shadcn-vue","unovue/shadcn-vue","Vue port of shadcn-ui","2023-06-26T13:53:23Z","2026-04-06T17:33:41Z","2026-04-06T17:33:39Z",9706,37,634,"dev",{"id":419,"title":376,"body":420,"description":1137,"extension":1138,"links":1139,"meta":1141,"navigation":132,"new":17,"path":377,"rawbody":1142,"seo":1143,"stem":378,"__hash__":1144},"content/docs/forms/02.tanstack-form.md",{"type":421,"value":422,"toc":1102},"minimark",[423,432,437,440,451,458,462,472,500,504,510,539,542,547,550,559,565,569,575,581,588,592,601,605,609,612,618,622,626,629,635,639,646,695,705,709,716,742,748,752,755,778,782,790,793,810,814,821,824,841,845,852,855,887,891,906,909,928,932,939,942,959,965,972,976,979,985,989,996,1002,1006,1012,1016,1019,1023,1028,1036,1040,1051,1057,1061,1067,1073,1077,1083,1089,1093,1096],[424,425,426,427,431],"p",{},"This guide explores how to build forms using TanStack Form. You'll learn to create forms with ",[428,429,430],"code",{},"\u003CField />"," components, implement schema validation with Zod, handle errors, and ensure accessibility.",[433,434,436],"h2",{"id":435},"demo","Demo",[424,438,439],{},"We'll start by building the following form. It has a simple text input and a textarea. On submit, we'll validate the form data and display any errors.",[441,442,444],"callout",{":icon":443},"true",[424,445,446,450],{},[447,448,449],"strong",{},"Note:"," For the purpose of this demo, we have intentionally disabled browser validation to show how schema validation and form errors work in TanStack Form. It is recommended to add basic browser validation in your production code.",[452,453],"component-preview",{":chromeLessOnMobile":443,"className":454,"name":457},[455,456],"sm:[&_.preview]:h-[700px]","sm:[&_pre]:!h-[700px]","TanStackFormDemo",[433,459,461],{"id":460},"approach","Approach",[424,463,464,465,467,468,471],{},"This form leverages TanStack Form for powerful, headless form handling. We'll build our form using the ",[428,466,430],{}," components, which give you ",[447,469,470],{},"complete flexibility over the markup and styling",".",[473,474,475,483,489,494,497],"ul",{},[476,477,478,479,482],"li",{},"Uses TanStack Form's ",[428,480,481],{},"useForm"," composable for form state management.",[476,484,485,488],{},[428,486,487],{},"form.Field"," components with render prop pattern for controlled inputs.",[476,490,491,493],{},[428,492,430],{}," components for building accessible forms.",[476,495,496],{},"Client-side validation using Zod.",[476,498,499],{},"Real-time validation feedback.",[433,501,503],{"id":502},"anatomy","Anatomy",[424,505,506,507,509],{},"Here's a basic example of a form using TanStack Form with the ",[428,508,430],{}," component.",[511,512,536],"pre",{"className":513,"code":515,"highlights":516,"language":534,"meta":535},[514],"language-vue","\u003Ctemplate>\n  \u003Cform\n    @submit.prevent=\"form.handleSubmit\"\n  >\n    \u003CFieldGroup>\n      \u003Cform.Field\n        name=\"title\"\n        #default=\"{ field }\"\n      >\n        \u003CField :data-invalid=\"isInvalid(field)\">\n          \u003CFieldLabel :for=\"field.name\">Bug Title\u003C/FieldLabel>\n          \u003CInput\n            :id=\"field.name\"\n            :name=\"field.name\"\n            :model-value=\"field.state.value\"\n            @blur=\"field.handleBlur\"\n            @input=\"field.handleChange($event.target.value)\"\n            :aria-invalid=\"isInvalid(field)\"\n            placeholder=\"Login button not working on mobile\"\n            autocomplete=\"off\"\n          />\n          \u003CFieldDescription>\n            Provide a concise title for your bug report.\n          \u003C/FieldDescription>\n          \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n        \u003C/Field>\n      \u003C/form.Field>\n    \u003C/FieldGroup>\n    \u003CButton type=\"submit\">Submit\u003C/Button>\n  \u003C/form>\n\u003C/template>\n",[517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533],10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,"vue","showLineNumbers",[428,537,515],{"__ignoreMap":538},"",[433,540,198],{"id":541},"form",[543,544,546],"h3",{"id":545},"create-a-form-schema","Create a form schema",[424,548,549],{},"We'll start by defining the shape of our form using a Zod schema.",[424,551,552,554,555,558],{},[447,553,449],{}," This example uses ",[428,556,557],{},"zod v3"," for schema validation. TanStack Form integrates seamlessly with Zod and other Standard Schema validation libraries through its validators API.",[511,560,563],{"className":561,"code":562,"language":534,"meta":535},[514],"\u003Cscript setup lang=\"ts\">\nimport { z } from 'zod'\n\nconst formSchema = z.object({\n  title: z\n    .string()\n    .min(5, 'Bug title must be at least 5 characters.')\n    .max(32, 'Bug title must be at most 32 characters.'),\n  description: z\n    .string()\n    .min(20, 'Description must be at least 20 characters.')\n    .max(100, 'Description must be at most 100 characters.'),\n})\n\u003C/script>\n",[428,564,562],{"__ignoreMap":538},[543,566,568],{"id":567},"setup-the-form","Setup the form",[424,570,571,572,574],{},"Use the ",[428,573,481],{}," composable from TanStack Form to create your form instance with Zod validation.",[511,576,579],{"className":577,"code":578,"language":534,"meta":535},[514],"\u003Cscript setup lang=\"ts\">\nimport { useForm } from '@tanstack/vue-form'\nimport { toast } from 'vue-sonner'\nimport { z } from 'zod'\n\nconst formSchema = z.object({\n  // ...\n})\n\nconst form = useForm({\n  defaultValues: {\n    title: '',\n    description: '',\n  },\n  validators: {\n    onSubmit: formSchema,\n  },\n  onSubmit: async ({ value }) => {\n    toast.success('Form submitted successfully')\n  },\n})\n\nfunction isInvalid(field) {\n  return field.state.meta.isTouched && !field.state.meta.isValid\n}\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cform @submit.prevent=\"form.handleSubmit\">\n    \u003C!-- ... -->\n  \u003C/form>\n\u003C/template>\n",[428,580,578],{"__ignoreMap":538},[424,582,583,584,587],{},"We are using ",[428,585,586],{},"onSubmit"," to validate the form data here. TanStack Form supports other validation modes, which you can read about in the documentation.",[543,589,591],{"id":590},"build-the-form","Build the form",[424,593,594,595,597,598,600],{},"We can now build the form using the ",[428,596,487],{}," component from TanStack Form and the ",[428,599,194],{}," components.",[602,603],"component-source",{"name":457,"title":604},"Form.vue",[543,606,608],{"id":607},"done","Done",[424,610,611],{},"That's it. You now have a fully accessible form with client-side validation.",[424,613,614,615,617],{},"When you submit the form, the ",[428,616,586],{}," function will be called with the validated form data. If the form data is invalid, TanStack Form will display the errors next to each field.",[433,619,621],{"id":620},"validation","Validation",[543,623,625],{"id":624},"client-side-validation","Client-side Validation",[424,627,628],{},"TanStack Form validates your form data using the Zod schema. Validation happens in real-time as the user types.",[511,630,633],{"className":631,"code":632,"language":534,"meta":535},[514],"\u003Cscript setup lang=\"ts\">\nimport { useForm } from '@tanstack/vue-form'\n\nconst formSchema = z.object({\n  // ...\n})\n\nconst form = useForm({\n  defaultValues: {\n    title: '',\n    description: '',\n  },\n  validators: {\n    onSubmit: formSchema,\n  },\n  onSubmit: async ({ value }) => {\n    console.log(value)\n  },\n})\n\u003C/script>\n",[428,634,632],{"__ignoreMap":538},[543,636,638],{"id":637},"validation-modes","Validation Modes",[424,640,641,642,645],{},"TanStack Form supports different validation strategies through the ",[428,643,644],{},"validators"," option:",[647,648,649,662],"table",{},[650,651,652],"thead",{},[653,654,655,659],"tr",{},[656,657,658],"th",{},"Mode",[656,660,661],{},"Description",[663,664,665,676,686],"tbody",{},[653,666,667,673],{},[668,669,670],"td",{},[428,671,672],{},"onChange",[668,674,675],{},"Validation triggers on every change.",[653,677,678,683],{},[668,679,680],{},[428,681,682],{},"onBlur",[668,684,685],{},"Validation triggers on blur.",[653,687,688,692],{},[668,689,690],{},[428,691,586],{},[668,693,694],{},"Validation triggers on submit.",[511,696,703],{"className":697,"code":698,"highlights":699,"language":534,"meta":535},[514],"\u003Cscript setup lang=\"ts\">\nconst form = useForm({\n  defaultValues: {\n    title: '',\n    description: '',\n  },\n  validators: {\n    onSubmit: formSchema,\n    onChange: formSchema,\n    onBlur: formSchema,\n  },\n})\n\u003C/script>\n",[700,701,702,517,518],7,8,9,[428,704,698],{"__ignoreMap":538},[433,706,708],{"id":707},"displaying-errors","Displaying Errors",[424,710,711,712,715],{},"Display errors next to the field using ",[428,713,714],{},"FieldError",". For styling and accessibility:",[473,717,718,727],{},[476,719,720,721,724,725,509],{},"Add the ",[428,722,723],{},":data-invalid"," prop to the ",[428,726,194],{},[476,728,720,729,732,733,735,736,735,739,741],{},[428,730,731],{},":aria-invalid"," prop to the form control such as ",[428,734,206],{},", ",[428,737,738],{},"SelectTrigger",[428,740,150],{},", etc.",[511,743,746],{"className":744,"code":745,"language":534,"meta":535},[514],"\u003Cscript setup lang=\"ts\">\nfunction isInvalid(field) {\n  return field.state.meta.isTouched && !field.state.meta.isValid\n}\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"email\"\n    #default=\"{ field }\"\n  >\n    \u003CField :data-invalid=\"isInvalid(field)\">\n      \u003CFieldLabel :for=\"field.name\">Email\u003C/FieldLabel>\n      \u003CInput\n        :id=\"field.name\"\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @blur=\"field.handleBlur\"\n        @input=\"field.handleChange($event.target.value)\"\n        type=\"email\"\n        :aria-invalid=\"isInvalid(field)\"\n      />\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n",[428,747,745],{"__ignoreMap":538},[433,749,751],{"id":750},"working-with-different-field-types","Working with Different Field Types",[543,753,206],{"id":754},"input",[424,756,757,758,761,762,765,766,768,769,724,771,773,774,724,776,509],{},"For input fields, use ",[428,759,760],{},"field.state.value"," and ",[428,763,764],{},"field.handleChange"," on the ",[428,767,206],{}," component.\nTo show errors, add the ",[428,770,731],{},[428,772,206],{}," component and the ",[428,775,723],{},[428,777,194],{},[452,779],{":chromeLessOnMobile":443,"className":780,"name":781},[455,456],"TanStackFormInput",[511,783,788],{"className":784,"code":785,"highlights":786,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003Cform.Field\n    name=\"username\"\n    #default=\"{ field }\"\n  >\n    \u003CField :data-invalid=\"isInvalid(field)\">\n      \u003CFieldLabel :for=\"`form-tanstack-input-username`\">Username\u003C/FieldLabel>\n      \u003CInput\n        id=\"form-tanstack-input-username\"\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @blur=\"field.handleBlur\"\n        @input=\"field.handleChange($event.target.value)\"\n        :aria-invalid=\"isInvalid(field)\"\n        placeholder=\"shadcn\"\n        autocomplete=\"username\"\n      />\n      \u003CFieldDescription>\n        This is your public display name. Must be between 3 and 10 characters.\n        Must only contain letters, numbers, and underscores.\n      \u003C/FieldDescription>\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n",[787,518,519,520,521,529],6,[428,789,785],{"__ignoreMap":538},[543,791,330],{"id":792},"textarea",[424,794,795,796,761,798,765,800,768,802,724,804,773,806,724,808,509],{},"For textarea fields, use ",[428,797,760],{},[428,799,764],{},[428,801,330],{},[428,803,731],{},[428,805,330],{},[428,807,723],{},[428,809,194],{},[452,811],{":chromeLessOnMobile":443,"className":812,"name":813},[455,456],"TanStackFormTextarea",[511,815,819],{"className":816,"code":817,"highlights":818,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003Cform.Field\n    name=\"about\"\n    #default=\"{ field }\"\n  >\n    \u003CField :data-invalid=\"isInvalid(field)\">\n      \u003CFieldLabel :for=\"`form-tanstack-textarea-about`\">\n        More about you\n      \u003C/FieldLabel>\n      \u003CTextarea\n        id=\"form-tanstack-textarea-about\"\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @blur=\"field.handleBlur\"\n        @input=\"field.handleChange($event.target.value)\"\n        :aria-invalid=\"isInvalid(field)\"\n        placeholder=\"I'm a software engineer...\"\n        class=\"min-h-[120px]\"\n      />\n      \u003CFieldDescription>\n        Tell us more about yourself. This will be used to help us\n        personalize your experience.\n      \u003C/FieldDescription>\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n",[787,520,521,522,523,531],[428,820,817],{"__ignoreMap":538},[543,822,278],{"id":823},"select",[424,825,826,827,761,829,765,831,768,833,724,835,773,837,724,839,509],{},"For select components, use ",[428,828,760],{},[428,830,764],{},[428,832,278],{},[428,834,731],{},[428,836,738],{},[428,838,723],{},[428,840,194],{},[452,842],{":chromeLessOnMobile":443,"className":843,"name":844},[455,456],"TanStackFormSelect",[511,846,850],{"className":847,"code":848,"highlights":849,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003Cform.Field\n    name=\"language\"\n    #default=\"{ field }\"\n  >\n    \u003CField orientation=\"responsive\" :data-invalid=\"isInvalid(field)\">\n      \u003CFieldContent>\n        \u003CFieldLabel :for=\"`form-tanstack-select-language`\">\n          Spoken Language\n        \u003C/FieldLabel>\n        \u003CFieldDescription>\n          For best results, select the language you speak.\n        \u003C/FieldDescription>\n        \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n      \u003C/FieldContent>\n      \u003CSelect\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @update:model-value=\"field.handleChange\"\n      >\n        \u003CSelectTrigger\n          id=\"form-tanstack-select-language\"\n          :aria-invalid=\"isInvalid(field)\"\n          class=\"min-w-[120px]\"\n        >\n          \u003CSelectValue placeholder=\"Select\" />\n        \u003C/SelectTrigger>\n        \u003CSelectContent position=\"item-aligned\">\n          \u003CSelectItem value=\"auto\">Auto\u003C/SelectItem>\n          \u003CSelectSeparator />\n          \u003CSelectItem\n            v-for=\"language in spokenLanguages\"\n            :key=\"language.value\"\n            :value=\"language.value\"\n          >\n            {{ language.label }}\n          \u003C/SelectItem>\n        \u003C/SelectContent>\n      \u003C/Select>\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n",[787,521,525,526,530],[428,851,848],{"__ignoreMap":538},[543,853,150],{"id":854},"checkbox",[424,856,857,858,761,860,765,862,768,864,724,866,773,868,724,870,872,873,765,876,878,879,882,883,886],{},"For checkboxes, use ",[428,859,760],{},[428,861,764],{},[428,863,150],{},[428,865,731],{},[428,867,150],{},[428,869,723],{},[428,871,194],{}," component.\nFor checkbox arrays, use ",[428,874,875],{},"mode=\"array\"",[428,877,487],{}," component and TanStack Form's array helpers.\nRemember to add ",[428,880,881],{},"data-slot=\"checkbox-group\""," to the ",[428,884,885],{},"FieldGroup"," component for proper styling and spacing.",[452,888],{":chromeLessOnMobile":443,"className":889,"name":890},[455,456],"TanStackFormCheckbox",[511,892,904],{"className":893,"code":894,"highlights":895,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003Cform.Field\n    name=\"tasks\"\n    mode=\"array\"\n    #default=\"{ field }\"\n  >\n    \u003CFieldSet>\n      \u003CFieldLegend variant=\"label\">Tasks\u003C/FieldLegend>\n      \u003CFieldDescription>\n        Get notified when tasks you've created have updates.\n      \u003C/FieldDescription>\n      \u003CFieldGroup data-slot=\"checkbox-group\">\n        \u003CField\n          v-for=\"task in tasks\"\n          :key=\"task.id\"\n          orientation=\"horizontal\"\n          :data-invalid=\"isInvalid(field)\"\n        >\n          \u003CCheckbox\n            :id=\"`form-tanstack-checkbox-${task.id}`\"\n            :name=\"field.name\"\n            :aria-invalid=\"isInvalid(field)\"\n            :model-value=\"field.state.value.includes(task.id)\"\n            @update:model-value=\"(checked | 'indeterminate') => {\n              if (checked) {\n                field.pushValue(task.id)\n              } else {\n                const index = field.state.value.indexOf(task.id)\n                if (index > -1) {\n                  field.removeValue(index)\n                }\n              }\n            }\"\n          />\n          \u003CFieldLabel\n            :for=\"`form-tanstack-checkbox-${task.id}`\"\n            class=\"font-normal\"\n          >\n            {{ task.label }}\n          \u003C/FieldLabel>\n        \u003C/Field>\n      \u003C/FieldGroup>\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/FieldSet>\n  \u003C/form.Field>\n\u003C/template>\n",[519,524,529,530,531,532,533,896,897,898,899,900,901,902,903],27,28,29,30,31,32,33,43,[428,905,894],{"__ignoreMap":538},[543,907,262],{"id":908},"radio-group",[424,910,911,912,761,914,765,916,768,919,724,921,773,924,724,926,509],{},"For radio groups, use ",[428,913,760],{},[428,915,764],{},[428,917,918],{},"RadioGroup",[428,920,731],{},[428,922,923],{},"RadioGroupItem",[428,925,723],{},[428,927,194],{},[452,929],{":chromeLessOnMobile":443,"className":930,"name":931},[455,456],"TanStackFormRadioGroup",[511,933,937],{"className":934,"code":935,"highlights":936,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003Cform.Field\n    name=\"plan\"\n    #default=\"{ field }\"\n  >\n    \u003CFieldSet>\n      \u003CFieldLegend>Plan\u003C/FieldLegend>\n      \u003CFieldDescription>\n        You can upgrade or downgrade your plan at any time.\n      \u003C/FieldDescription>\n      \u003CRadioGroup\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @update:model-value=\"field.handleChange\"\n      >\n        \u003CFieldLabel\n          v-for=\"plan in plans\"\n          :key=\"plan.id\"\n          :for=\"`form-tanstack-radiogroup-${plan.id}`\"\n        >\n          \u003CField\n            orientation=\"horizontal\"\n            :data-invalid=\"isInvalid(field)\"\n          >\n            \u003CFieldContent>\n              \u003CFieldTitle>{{ plan.title }}\u003C/FieldTitle>\n              \u003CFieldDescription>{{ plan.description }}\u003C/FieldDescription>\n            \u003C/FieldContent>\n            \u003CRadioGroupItem\n              :value=\"plan.id\"\n              :id=\"`form-tanstack-radiogroup-${plan.id}`\"\n              :aria-invalid=\"isInvalid(field)\"\n            />\n          \u003C/Field>\n        \u003C/FieldLabel>\n      \u003C/RadioGroup>\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/FieldSet>\n  \u003C/form.Field>\n\u003C/template>\n",[520,521,530,901,415],[428,938,935],{"__ignoreMap":538},[543,940,314],{"id":941},"switch",[424,943,944,945,761,947,765,949,768,951,724,953,773,955,724,957,509],{},"For switches, use ",[428,946,760],{},[428,948,764],{},[428,950,314],{},[428,952,731],{},[428,954,314],{},[428,956,723],{},[428,958,194],{},[452,960],{":chromeLessOnMobile":443,"className":961,"name":964},[962,963],"sm:[&_.preview]:h-[500px]","sm:[&_pre]:!h-[500px]","TanStackFormSwitch",[511,966,970],{"className":967,"code":968,"highlights":969,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003Cform.Field\n    name=\"twoFactor\"\n    #default=\"{ field }\"\n  >\n    \u003CField orientation=\"horizontal\" :data-invalid=\"isInvalid(field)\">\n      \u003CFieldContent>\n        \u003CFieldLabel :for=\"field.name\">\n          Multi-factor authentication\n        \u003C/FieldLabel>\n        \u003CFieldDescription>\n          Enable multi-factor authentication to secure your account.\n        \u003C/FieldDescription>\n        \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n      \u003C/FieldContent>\n      \u003CSwitch\n        :id=\"field.name\"\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @update:model-value=\"field.handleChange\"\n        :aria-invalid=\"isInvalid(field)\"\n      />\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n",[787,521,526,527,528],[428,971,968],{"__ignoreMap":538},[543,973,975],{"id":974},"complex-forms","Complex Forms",[424,977,978],{},"Here is an example of a more complex form with multiple fields and validation.",[452,980],{":chromeLessOnMobile":443,"className":981,"name":984},[982,983],"sm:[&_.preview]:h-[1100px]","sm:[&_pre]:!h-[1100px]","TanStackFormComplex",[433,986,988],{"id":987},"resetting-the-form","Resetting the Form",[424,990,991,992,995],{},"Use ",[428,993,994],{},"form.reset()"," to reset the form to its default values.",[511,997,1000],{"className":998,"code":999,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003CButton type=\"button\" variant=\"outline\" @click=\"form.reset()\">\n    Reset\n  \u003C/Button>\n\u003C/template>\n",[428,1001,999],{"__ignoreMap":538},[433,1003,1005],{"id":1004},"array-fields","Array Fields",[424,1007,1008,1009,1011],{},"TanStack Form provides powerful array field management with ",[428,1010,875],{},". This allows you to dynamically add, remove, and update array items with full validation support.",[452,1013],{":chromeLessOnMobile":443,"className":1014,"name":1015},[455,456],"TanStackFormArray",[424,1017,1018],{},"This example demonstrates managing multiple email addresses with array fields. Users can add up to 5 email addresses, remove individual addresses, and each address is validated independently.",[543,1020,1022],{"id":1021},"using-fieldarray","Using FieldArray",[424,1024,991,1025,1027],{},[428,1026,875],{}," on the parent field to enable array field management.",[511,1029,1034],{"className":1030,"code":1031,"highlights":1032,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003Cform.Field\n    name=\"emails\"\n    mode=\"array\"\n    #default=\"{ field }\"\n  >\n    \u003CFieldSet>\n      \u003CFieldLegend variant=\"label\">Email Addresses\u003C/FieldLegend>\n      \u003CFieldDescription>\n        Add up to 5 email addresses where we can contact you.\n      \u003C/FieldDescription>\n      \u003CFieldGroup>\n        \u003Ctemplate v-for=\"(_, index) in field.state.value\">\n          \u003C!-- Nested field for each array item -->\n        \u003C/template>\n      \u003C/FieldGroup>\n    \u003C/FieldSet>\n  \u003C/form.Field>\n\u003C/template>\n",[1033,520,521,522],4,[428,1035,1031],{"__ignoreMap":538},[543,1037,1039],{"id":1038},"nested-fields","Nested Fields",[424,1041,1042,1043,1046,1047,1050],{},"Access individual array items using bracket notation: ",[428,1044,1045],{},"fieldName[index].propertyName",". This example uses ",[428,1048,1049],{},"InputGroup"," to display the remove button inline with the input.",[511,1052,1055],{"className":1053,"code":1054,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003Cform.Field\n    :name=\"`emails[${index}].address`\"\n    #default=\"{ subField }\"\n  >\n    \u003CField orientation=\"horizontal\" :data-invalid=\"isSubFieldInvalid(subField)\">\n      \u003CFieldContent>\n        \u003CInputGroup>\n          \u003CInputGroupInput\n            :id=\"`form-tanstack-array-email-${index}`\"\n            :name=\"subField.name\"\n            :model-value=\"subField.state.value\"\n            @blur=\"subField.handleBlur\"\n            @input=\"subField.handleChange($event.target.value)\"\n            :aria-invalid=\"isSubFieldInvalid(subField)\"\n            placeholder=\"name@example.com\"\n            type=\"email\"\n          />\n          \u003CInputGroupAddon v-if=\"field.state.value.length > 1\" align=\"inline-end\">\n            \u003CInputGroupButton\n              type=\"button\"\n              variant=\"ghost\"\n              size=\"icon-xs\"\n              @click=\"field.removeValue(index)\"\n              :aria-label=\"`Remove email ${index + 1}`\"\n            >\n              \u003CXIcon />\n            \u003C/InputGroupButton>\n          \u003C/InputGroupAddon>\n        \u003C/InputGroup>\n        \u003CFieldError v-if=\"isSubFieldInvalid(subField)\" :errors=\"subField.state.meta.errors\" />\n      \u003C/FieldContent>\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n",[428,1056,1054],{"__ignoreMap":538},[543,1058,1060],{"id":1059},"adding-items","Adding Items",[424,1062,991,1063,1066],{},[428,1064,1065],{},"field.pushValue(item)"," to add items to an array field. You can disable the button when the array reaches its maximum length.",[511,1068,1071],{"className":1069,"code":1070,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003CButton\n    type=\"button\"\n    variant=\"outline\"\n    size=\"sm\"\n    @click=\"field.pushValue({ address: '' })\"\n    :disabled=\"field.state.value.length >= 5\"\n  >\n    Add Email Address\n  \u003C/Button>\n\u003C/template>\n",[428,1072,1070],{"__ignoreMap":538},[543,1074,1076],{"id":1075},"removing-items","Removing Items",[424,1078,991,1079,1082],{},[428,1080,1081],{},"field.removeValue(index)"," to remove items from an array field. You can conditionally show the remove button only when there's more than one item.",[511,1084,1087],{"className":1085,"code":1086,"language":534,"meta":535},[514],"\u003Ctemplate>\n  \u003CInputGroupButton\n    v-if=\"field.state.value.length > 1\"\n    @click=\"field.removeValue(index)\"\n    :aria-label=\"`Remove email ${index + 1}`\"\n  >\n    \u003CXIcon />\n  \u003C/InputGroupButton>\n\u003C/template>\n",[428,1088,1086],{"__ignoreMap":538},[543,1090,1092],{"id":1091},"array-validation","Array Validation",[424,1094,1095],{},"Validate array fields using Zod's array methods.",[511,1097,1100],{"className":1098,"code":1099,"language":534,"meta":535},[514],"\u003Cscript setup lang=\"ts\">\nconst formSchema = z.object({\n  emails: z\n    .array(\n      z.object({\n        address: z.string().email('Enter a valid email address.'),\n      })\n    )\n    .min(1, 'Add at least one email address.')\n    .max(5, 'You can add up to 5 email addresses.'),\n})\n\u003C/script>\n",[428,1101,1099],{"__ignoreMap":538},{"title":538,"searchDepth":1103,"depth":1103,"links":1104},2,[1105,1106,1107,1108,1115,1119,1120,1129,1130],{"id":435,"depth":1103,"text":436},{"id":460,"depth":1103,"text":461},{"id":502,"depth":1103,"text":503},{"id":541,"depth":1103,"text":198,"children":1109},[1110,1112,1113,1114],{"id":545,"depth":1111,"text":546},3,{"id":567,"depth":1111,"text":568},{"id":590,"depth":1111,"text":591},{"id":607,"depth":1111,"text":608},{"id":620,"depth":1103,"text":621,"children":1116},[1117,1118],{"id":624,"depth":1111,"text":625},{"id":637,"depth":1111,"text":638},{"id":707,"depth":1103,"text":708},{"id":750,"depth":1103,"text":751,"children":1121},[1122,1123,1124,1125,1126,1127,1128],{"id":754,"depth":1111,"text":206},{"id":792,"depth":1111,"text":330},{"id":823,"depth":1111,"text":278},{"id":854,"depth":1111,"text":150},{"id":908,"depth":1111,"text":262},{"id":941,"depth":1111,"text":314},{"id":974,"depth":1111,"text":975},{"id":987,"depth":1103,"text":988},{"id":1004,"depth":1103,"text":1005,"children":1131},[1132,1133,1134,1135,1136],{"id":1021,"depth":1111,"text":1022},{"id":1038,"depth":1111,"text":1039},{"id":1059,"depth":1111,"text":1060},{"id":1075,"depth":1111,"text":1076},{"id":1091,"depth":1111,"text":1092},"Build forms in Vue using TanStack Form and Zod.","md",{"doc":1140},"https://tanstack.com/form",{},"---\ntitle: TanStack Form\ndescription: Build forms in Vue using TanStack Form and Zod.\nlinks:\n  doc: https://tanstack.com/form\n---\n\nThis guide explores how to build forms using TanStack Form. You'll learn to create forms with `\u003CField />` components, implement schema validation with Zod, handle errors, and ensure accessibility.\n\n## Demo\n\nWe'll start by building the following form. It has a simple text input and a textarea. On submit, we'll validate the form data and display any errors.\n\n::callout\n---\nicon: true\n---\n**Note:** For the purpose of this demo, we have intentionally disabled browser validation to show how schema validation and form errors work in TanStack Form. It is recommended to add basic browser validation in your production code.\n::\n\n::component-preview\n---\nname: TanStackFormDemo\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n## Approach\n\nThis form leverages TanStack Form for powerful, headless form handling. We'll build our form using the `\u003CField />` components, which give you **complete flexibility over the markup and styling**.\n\n- Uses TanStack Form's `useForm` composable for form state management.\n- `form.Field` components with render prop pattern for controlled inputs.\n- `\u003CField />` components for building accessible forms.\n- Client-side validation using Zod.\n- Real-time validation feedback.\n\n## Anatomy\n\nHere's a basic example of a form using TanStack Form with the `\u003CField />` component.\n\n```vue showLineNumbers {10-26}\n\u003Ctemplate>\n  \u003Cform\n    @submit.prevent=\"form.handleSubmit\"\n  >\n    \u003CFieldGroup>\n      \u003Cform.Field\n        name=\"title\"\n        #default=\"{ field }\"\n      >\n        \u003CField :data-invalid=\"isInvalid(field)\">\n          \u003CFieldLabel :for=\"field.name\">Bug Title\u003C/FieldLabel>\n          \u003CInput\n            :id=\"field.name\"\n            :name=\"field.name\"\n            :model-value=\"field.state.value\"\n            @blur=\"field.handleBlur\"\n            @input=\"field.handleChange($event.target.value)\"\n            :aria-invalid=\"isInvalid(field)\"\n            placeholder=\"Login button not working on mobile\"\n            autocomplete=\"off\"\n          />\n          \u003CFieldDescription>\n            Provide a concise title for your bug report.\n          \u003C/FieldDescription>\n          \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n        \u003C/Field>\n      \u003C/form.Field>\n    \u003C/FieldGroup>\n    \u003CButton type=\"submit\">Submit\u003C/Button>\n  \u003C/form>\n\u003C/template>\n```\n\n## Form\n\n### Create a form schema\n\nWe'll start by defining the shape of our form using a Zod schema.\n\n**Note:** This example uses `zod v3` for schema validation. TanStack Form integrates seamlessly with Zod and other Standard Schema validation libraries through its validators API.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nimport { z } from 'zod'\n\nconst formSchema = z.object({\n  title: z\n    .string()\n    .min(5, 'Bug title must be at least 5 characters.')\n    .max(32, 'Bug title must be at most 32 characters.'),\n  description: z\n    .string()\n    .min(20, 'Description must be at least 20 characters.')\n    .max(100, 'Description must be at most 100 characters.'),\n})\n\u003C/script>\n```\n\n### Setup the form\n\nUse the `useForm` composable from TanStack Form to create your form instance with Zod validation.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nimport { useForm } from '@tanstack/vue-form'\nimport { toast } from 'vue-sonner'\nimport { z } from 'zod'\n\nconst formSchema = z.object({\n  // ...\n})\n\nconst form = useForm({\n  defaultValues: {\n    title: '',\n    description: '',\n  },\n  validators: {\n    onSubmit: formSchema,\n  },\n  onSubmit: async ({ value }) => {\n    toast.success('Form submitted successfully')\n  },\n})\n\nfunction isInvalid(field) {\n  return field.state.meta.isTouched && !field.state.meta.isValid\n}\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cform @submit.prevent=\"form.handleSubmit\">\n    \u003C!-- ... -->\n  \u003C/form>\n\u003C/template>\n```\n\nWe are using `onSubmit` to validate the form data here. TanStack Form supports other validation modes, which you can read about in the documentation.\n\n### Build the form\n\nWe can now build the form using the `form.Field` component from TanStack Form and the `Field` components.\n\n::component-source\n---\nname: TanStackFormDemo\ntitle: Form.vue\n---\n::\n\n### Done\n\nThat's it. You now have a fully accessible form with client-side validation.\n\nWhen you submit the form, the `onSubmit` function will be called with the validated form data. If the form data is invalid, TanStack Form will display the errors next to each field.\n\n## Validation\n\n### Client-side Validation\n\nTanStack Form validates your form data using the Zod schema. Validation happens in real-time as the user types.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nimport { useForm } from '@tanstack/vue-form'\n\nconst formSchema = z.object({\n  // ...\n})\n\nconst form = useForm({\n  defaultValues: {\n    title: '',\n    description: '',\n  },\n  validators: {\n    onSubmit: formSchema,\n  },\n  onSubmit: async ({ value }) => {\n    console.log(value)\n  },\n})\n\u003C/script>\n```\n\n### Validation Modes\n\nTanStack Form supports different validation strategies through the `validators` option:\n\n| Mode       | Description                           |\n| ---------- | ------------------------------------- |\n| `onChange` | Validation triggers on every change. |\n| `onBlur`   | Validation triggers on blur.         |\n| `onSubmit` | Validation triggers on submit.       |\n\n```vue showLineNumbers {7-11}\n\u003Cscript setup lang=\"ts\">\nconst form = useForm({\n  defaultValues: {\n    title: '',\n    description: '',\n  },\n  validators: {\n    onSubmit: formSchema,\n    onChange: formSchema,\n    onBlur: formSchema,\n  },\n})\n\u003C/script>\n```\n\n## Displaying Errors\n\nDisplay errors next to the field using `FieldError`. For styling and accessibility:\n- Add the `:data-invalid` prop to the `Field` component.\n- Add the `:aria-invalid` prop to the form control such as `Input`, `SelectTrigger`, `Checkbox`, etc.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nfunction isInvalid(field) {\n  return field.state.meta.isTouched && !field.state.meta.isValid\n}\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"email\"\n    #default=\"{ field }\"\n  >\n    \u003CField :data-invalid=\"isInvalid(field)\">\n      \u003CFieldLabel :for=\"field.name\">Email\u003C/FieldLabel>\n      \u003CInput\n        :id=\"field.name\"\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @blur=\"field.handleBlur\"\n        @input=\"field.handleChange($event.target.value)\"\n        type=\"email\"\n        :aria-invalid=\"isInvalid(field)\"\n      />\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n## Working with Different Field Types\n\n### Input\n\nFor input fields, use `field.state.value` and `field.handleChange` on the `Input` component.\nTo show errors, add the `:aria-invalid` prop to the `Input` component and the `:data-invalid` prop to the `Field` component.\n\n::component-preview\n---\nname: TanStackFormInput\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers {6, 11-14, 22}\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"username\"\n    #default=\"{ field }\"\n  >\n    \u003CField :data-invalid=\"isInvalid(field)\">\n      \u003CFieldLabel :for=\"`form-tanstack-input-username`\">Username\u003C/FieldLabel>\n      \u003CInput\n        id=\"form-tanstack-input-username\"\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @blur=\"field.handleBlur\"\n        @input=\"field.handleChange($event.target.value)\"\n        :aria-invalid=\"isInvalid(field)\"\n        placeholder=\"shadcn\"\n        autocomplete=\"username\"\n      />\n      \u003CFieldDescription>\n        This is your public display name. Must be between 3 and 10 characters.\n        Must only contain letters, numbers, and underscores.\n      \u003C/FieldDescription>\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n### Textarea\n\nFor textarea fields, use `field.state.value` and `field.handleChange` on the `Textarea` component.\nTo show errors, add the `:aria-invalid` prop to the `Textarea` component and the `:data-invalid` prop to the `Field` component.\n\n::component-preview\n---\nname: TanStackFormTextarea\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers {6,13-16,24}\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"about\"\n    #default=\"{ field }\"\n  >\n    \u003CField :data-invalid=\"isInvalid(field)\">\n      \u003CFieldLabel :for=\"`form-tanstack-textarea-about`\">\n        More about you\n      \u003C/FieldLabel>\n      \u003CTextarea\n        id=\"form-tanstack-textarea-about\"\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @blur=\"field.handleBlur\"\n        @input=\"field.handleChange($event.target.value)\"\n        :aria-invalid=\"isInvalid(field)\"\n        placeholder=\"I'm a software engineer...\"\n        class=\"min-h-[120px]\"\n      />\n      \u003CFieldDescription>\n        Tell us more about yourself. This will be used to help us\n        personalize your experience.\n      \u003C/FieldDescription>\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n### Select\n\nFor select components, use `field.state.value` and `field.handleChange` on the `Select` component.\nTo show errors, add the `:aria-invalid` prop to the `SelectTrigger` component and the `:data-invalid` prop to the `Field` component.\n\n::component-preview\n---\nname: TanStackFormSelect\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers {6, 14, 18-19, 23}\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"language\"\n    #default=\"{ field }\"\n  >\n    \u003CField orientation=\"responsive\" :data-invalid=\"isInvalid(field)\">\n      \u003CFieldContent>\n        \u003CFieldLabel :for=\"`form-tanstack-select-language`\">\n          Spoken Language\n        \u003C/FieldLabel>\n        \u003CFieldDescription>\n          For best results, select the language you speak.\n        \u003C/FieldDescription>\n        \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n      \u003C/FieldContent>\n      \u003CSelect\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @update:model-value=\"field.handleChange\"\n      >\n        \u003CSelectTrigger\n          id=\"form-tanstack-select-language\"\n          :aria-invalid=\"isInvalid(field)\"\n          class=\"min-w-[120px]\"\n        >\n          \u003CSelectValue placeholder=\"Select\" />\n        \u003C/SelectTrigger>\n        \u003CSelectContent position=\"item-aligned\">\n          \u003CSelectItem value=\"auto\">Auto\u003C/SelectItem>\n          \u003CSelectSeparator />\n          \u003CSelectItem\n            v-for=\"language in spokenLanguages\"\n            :key=\"language.value\"\n            :value=\"language.value\"\n          >\n            {{ language.label }}\n          \u003C/SelectItem>\n        \u003C/SelectContent>\n      \u003C/Select>\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n### Checkbox\n\nFor checkboxes, use `field.state.value` and `field.handleChange` on the `Checkbox` component.\nTo show errors, add the `:aria-invalid` prop to the `Checkbox` component and the `:data-invalid` prop to the `Field` component.\nFor checkbox arrays, use `mode=\"array\"` on the `form.Field` component and TanStack Form's array helpers.\nRemember to add `data-slot=\"checkbox-group\"` to the `FieldGroup` component for proper styling and spacing.\n\n::component-preview\n---\nname: TanStackFormCheckbox\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers {12, 17, 22-33, 43}\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"tasks\"\n    mode=\"array\"\n    #default=\"{ field }\"\n  >\n    \u003CFieldSet>\n      \u003CFieldLegend variant=\"label\">Tasks\u003C/FieldLegend>\n      \u003CFieldDescription>\n        Get notified when tasks you've created have updates.\n      \u003C/FieldDescription>\n      \u003CFieldGroup data-slot=\"checkbox-group\">\n        \u003CField\n          v-for=\"task in tasks\"\n          :key=\"task.id\"\n          orientation=\"horizontal\"\n          :data-invalid=\"isInvalid(field)\"\n        >\n          \u003CCheckbox\n            :id=\"`form-tanstack-checkbox-${task.id}`\"\n            :name=\"field.name\"\n            :aria-invalid=\"isInvalid(field)\"\n            :model-value=\"field.state.value.includes(task.id)\"\n            @update:model-value=\"(checked | 'indeterminate') => {\n              if (checked) {\n                field.pushValue(task.id)\n              } else {\n                const index = field.state.value.indexOf(task.id)\n                if (index > -1) {\n                  field.removeValue(index)\n                }\n              }\n            }\"\n          />\n          \u003CFieldLabel\n            :for=\"`form-tanstack-checkbox-${task.id}`\"\n            class=\"font-normal\"\n          >\n            {{ task.label }}\n          \u003C/FieldLabel>\n        \u003C/Field>\n      \u003C/FieldGroup>\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/FieldSet>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n### Radio Group\n\nFor radio groups, use `field.state.value` and `field.handleChange` on the `RadioGroup` component.\nTo show errors, add the `:aria-invalid` prop to the `RadioGroupItem` component and the `:data-invalid` prop to the `Field` component.\n\n::component-preview\n---\nname: TanStackFormRadioGroup\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers {13-14, 23, 32, 37}\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"plan\"\n    #default=\"{ field }\"\n  >\n    \u003CFieldSet>\n      \u003CFieldLegend>Plan\u003C/FieldLegend>\n      \u003CFieldDescription>\n        You can upgrade or downgrade your plan at any time.\n      \u003C/FieldDescription>\n      \u003CRadioGroup\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @update:model-value=\"field.handleChange\"\n      >\n        \u003CFieldLabel\n          v-for=\"plan in plans\"\n          :key=\"plan.id\"\n          :for=\"`form-tanstack-radiogroup-${plan.id}`\"\n        >\n          \u003CField\n            orientation=\"horizontal\"\n            :data-invalid=\"isInvalid(field)\"\n          >\n            \u003CFieldContent>\n              \u003CFieldTitle>{{ plan.title }}\u003C/FieldTitle>\n              \u003CFieldDescription>{{ plan.description }}\u003C/FieldDescription>\n            \u003C/FieldContent>\n            \u003CRadioGroupItem\n              :value=\"plan.id\"\n              :id=\"`form-tanstack-radiogroup-${plan.id}`\"\n              :aria-invalid=\"isInvalid(field)\"\n            />\n          \u003C/Field>\n        \u003C/FieldLabel>\n      \u003C/RadioGroup>\n      \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n    \u003C/FieldSet>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n### Switch\n\nFor switches, use `field.state.value` and `field.handleChange` on the `Switch` component.\nTo show errors, add the `:aria-invalid` prop to the `Switch` component and the `:data-invalid` prop to the `Field` component.\n\n::component-preview\n---\nname: TanStackFormSwitch\nclass: sm:[&_.preview]:h-[500px] sm:[&_pre]:!h-[500px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers {6, 14, 19-21}\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"twoFactor\"\n    #default=\"{ field }\"\n  >\n    \u003CField orientation=\"horizontal\" :data-invalid=\"isInvalid(field)\">\n      \u003CFieldContent>\n        \u003CFieldLabel :for=\"field.name\">\n          Multi-factor authentication\n        \u003C/FieldLabel>\n        \u003CFieldDescription>\n          Enable multi-factor authentication to secure your account.\n        \u003C/FieldDescription>\n        \u003CFieldError v-if=\"isInvalid(field)\" :errors=\"field.state.meta.errors\" />\n      \u003C/FieldContent>\n      \u003CSwitch\n        :id=\"field.name\"\n        :name=\"field.name\"\n        :model-value=\"field.state.value\"\n        @update:model-value=\"field.handleChange\"\n        :aria-invalid=\"isInvalid(field)\"\n      />\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n### Complex Forms\n\nHere is an example of a more complex form with multiple fields and validation.\n\n::component-preview\n---\nname: TanStackFormComplex\nclass: sm:[&_.preview]:h-[1100px] sm:[&_pre]:!h-[1100px]\nchromeLessOnMobile: true\n---\n::\n\n## Resetting the Form\n\nUse `form.reset()` to reset the form to its default values.\n\n```vue showLineNumbers\n\u003Ctemplate>\n  \u003CButton type=\"button\" variant=\"outline\" @click=\"form.reset()\">\n    Reset\n  \u003C/Button>\n\u003C/template>\n```\n\n## Array Fields\n\nTanStack Form provides powerful array field management with `mode=\"array\"`. This allows you to dynamically add, remove, and update array items with full validation support.\n\n::component-preview\n---\nname: TanStackFormArray\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\nThis example demonstrates managing multiple email addresses with array fields. Users can add up to 5 email addresses, remove individual addresses, and each address is validated independently.\n\n### Using FieldArray\n\nUse `mode=\"array\"` on the parent field to enable array field management.\n\n```vue showLineNumbers {4, 13-15}\n\u003Ctemplate>\n  \u003Cform.Field\n    name=\"emails\"\n    mode=\"array\"\n    #default=\"{ field }\"\n  >\n    \u003CFieldSet>\n      \u003CFieldLegend variant=\"label\">Email Addresses\u003C/FieldLegend>\n      \u003CFieldDescription>\n        Add up to 5 email addresses where we can contact you.\n      \u003C/FieldDescription>\n      \u003CFieldGroup>\n        \u003Ctemplate v-for=\"(_, index) in field.state.value\">\n          \u003C!-- Nested field for each array item -->\n        \u003C/template>\n      \u003C/FieldGroup>\n    \u003C/FieldSet>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n### Nested Fields\n\nAccess individual array items using bracket notation: `fieldName[index].propertyName`. This example uses `InputGroup` to display the remove button inline with the input.\n\n```vue showLineNumbers\n\u003Ctemplate>\n  \u003Cform.Field\n    :name=\"`emails[${index}].address`\"\n    #default=\"{ subField }\"\n  >\n    \u003CField orientation=\"horizontal\" :data-invalid=\"isSubFieldInvalid(subField)\">\n      \u003CFieldContent>\n        \u003CInputGroup>\n          \u003CInputGroupInput\n            :id=\"`form-tanstack-array-email-${index}`\"\n            :name=\"subField.name\"\n            :model-value=\"subField.state.value\"\n            @blur=\"subField.handleBlur\"\n            @input=\"subField.handleChange($event.target.value)\"\n            :aria-invalid=\"isSubFieldInvalid(subField)\"\n            placeholder=\"name@example.com\"\n            type=\"email\"\n          />\n          \u003CInputGroupAddon v-if=\"field.state.value.length > 1\" align=\"inline-end\">\n            \u003CInputGroupButton\n              type=\"button\"\n              variant=\"ghost\"\n              size=\"icon-xs\"\n              @click=\"field.removeValue(index)\"\n              :aria-label=\"`Remove email ${index + 1}`\"\n            >\n              \u003CXIcon />\n            \u003C/InputGroupButton>\n          \u003C/InputGroupAddon>\n        \u003C/InputGroup>\n        \u003CFieldError v-if=\"isSubFieldInvalid(subField)\" :errors=\"subField.state.meta.errors\" />\n      \u003C/FieldContent>\n    \u003C/Field>\n  \u003C/form.Field>\n\u003C/template>\n```\n\n\n### Adding Items\n\nUse `field.pushValue(item)` to add items to an array field. You can disable the button when the array reaches its maximum length.\n\n```vue showLineNumbers\n\u003Ctemplate>\n  \u003CButton\n    type=\"button\"\n    variant=\"outline\"\n    size=\"sm\"\n    @click=\"field.pushValue({ address: '' })\"\n    :disabled=\"field.state.value.length >= 5\"\n  >\n    Add Email Address\n  \u003C/Button>\n\u003C/template>\n```\n\n### Removing Items\n\nUse `field.removeValue(index)` to remove items from an array field. You can conditionally show the remove button only when there's more than one item.\n\n```vue showLineNumbers\n\u003Ctemplate>\n  \u003CInputGroupButton\n    v-if=\"field.state.value.length > 1\"\n    @click=\"field.removeValue(index)\"\n    :aria-label=\"`Remove email ${index + 1}`\"\n  >\n    \u003CXIcon />\n  \u003C/InputGroupButton>\n\u003C/template>\n```\n\n### Array Validation\n\nValidate array fields using Zod's array methods.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nconst formSchema = z.object({\n  emails: z\n    .array(\n      z.object({\n        address: z.string().email('Enter a valid email address.'),\n      })\n    )\n    .min(1, 'Add at least one email address.')\n    .max(5, 'You can add up to 5 email addresses.'),\n})\n\u003C/script>\n```\n",{"title":376,"description":1137},"i4B2cH0WhXNIvC4Sba-SP45seeXMgkdkflUBcmL9vys",[1146,1147],{"title":372,"path":373,"stem":374,"children":-1},{"title":1148,"path":1149,"stem":1150,"children":-1},"MCP Server","/docs/mcp","docs/mcp",1775650001386]