[{"data":1,"prerenderedAt":1237},["ShallowReactive",2],{"navigation":3,"$fMW9jOOkNpZriWVK3P_bZYNOyYR2IEPvrHsC7cDBlJJc":405,"/docs/forms/vee-validate":418,"surround-/docs/forms/vee-validate":1234},[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":372,"body":420,"description":1226,"extension":1227,"links":1228,"meta":1230,"navigation":132,"new":17,"path":373,"rawbody":1231,"seo":1232,"stem":374,"__hash__":1233},"content/docs/forms/01.vee-validate.md",{"type":421,"value":422,"toc":1192},"minimark",[423,432,442,447,450,461,468,472,482,511,515,524,554,557,562,565,576,583,587,593,604,608,615,619,623,626,633,637,641,651,659,663,669,676,736,740,747,776,783,787,790,812,816,822,829,832,851,855,860,867,870,893,899,906,909,938,942,952,955,975,979,987,990,1011,1015,1022,1026,1029,1035,1039,1049,1055,1059,1066,1070,1074,1090,1097,1101,1114,1120,1124,1137,1143,1147,1152,1158,1162,1167,1173,1177,1184],[424,425,426,427,431],"p",{},"In this guide, we will take a look at building forms with VeeValidate. We'll cover building forms with the ",[428,429,430],"code",{},"\u003CField />"," component, adding schema validation using Zod, error handling, accessibility, and more.",[433,434,439],"vue-school-link",{"className":435,"lesson":437,"placement":438},[436],"mt-6","forms-and-form-validation-with-shadcn-vue","top",[424,440,441],{},"Watch a Vue School video about building forms and validation with shadcn-vue.",[443,444,446],"h2",{"id":445},"demo","Demo",[424,448,449],{},"We are going to build the following form. It has a simple text input and a textarea. On submit, we'll validate the form data and display any errors.",[451,452,454],"callout",{":icon":453},"true",[424,455,456,460],{},[457,458,459],"strong",{},"Note:"," For the purpose of this demo, we have intentionally disabled browser validation to show how schema validation and form errors work in VeeValidate. It is recommended to add basic browser validation in your production code.",[462,463],"component-preview",{":chromeLessOnMobile":453,"className":464,"name":467},[465,466],"sm:[&_.preview]:h-[700px]","sm:[&_pre]:!h-[700px]","VeeValidateDemo",[443,469,471],{"id":470},"approach","Approach",[424,473,474,475,477,478,481],{},"This form leverages VeeValidate for performant, flexible form handling. We'll build our form using the ",[428,476,430],{}," component, which gives you ",[457,479,480],{},"complete flexibility over the markup and styling",".",[483,484,485,493,499,505],"ul",{},[486,487,488,489,492],"li",{},"Uses VeeValidate's ",[428,490,491],{},"useForm"," composable for form state management.",[486,494,495,496,498],{},"VeeValidate's ",[428,497,430],{}," component with scoped slots for controlled inputs with validation.",[486,500,501,502,504],{},"shadcn-vue ",[428,503,430],{}," components for building accessible forms.",[486,506,507,508,481],{},"Client-side validation using Zod with ",[428,509,510],{},"toTypedSchema",[443,512,514],{"id":513},"anatomy","Anatomy",[424,516,517,518,520,521,523],{},"Here's a basic example of a form using VeeValidate's ",[428,519,430],{}," component with scoped slots and shadcn-vue ",[428,522,430],{}," components.",[525,526,551],"pre",{"className":527,"code":529,"highlights":530,"language":549,"meta":550},[528],"language-vue","\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"title\">\n    \u003CField :data-invalid=\"!!errors.length\">\n      \u003CFieldLabel for=\"title\">\n        Bug Title\n      \u003C/FieldLabel>\n      \u003CInput\n        id=\"title\"\n        v-bind=\"field\"\n        placeholder=\"Login button not working on mobile\"\n        autocomplete=\"off\"\n        :aria-invalid=\"!!errors.length\"\n      />\n      \u003CFieldDescription>\n        Provide a concise title for your bug report.\n      \u003C/FieldDescription>\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n",[531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548],2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,"vue","showLineNumbers",[428,552,529],{"__ignoreMap":553},"",[443,555,198],{"id":556},"form",[558,559,561],"h3",{"id":560},"create-a-form-schema","Create a form schema",[424,563,564],{},"We'll start by defining the shape of our form using a Zod schema",[451,566,567],{":icon":453},[424,568,569,571,572,575],{},[457,570,459],{}," This example uses ",[428,573,574],{},"zod v3"," for schema validation, but you can replace it with any other Standard Schema validation library supported by VeeValidate.",[525,577,581],{"className":578,"code":579,"language":549,"meta":580},[528],"\u003Cscript setup lang=\"ts\">\nimport * as 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","showLineNumbers title=\"Form.vue\"",[428,582,579],{"__ignoreMap":553},[558,584,586],{"id":585},"setup-the-form","Setup the form",[424,588,589,590,592],{},"Next, we'll use the ",[428,591,491],{}," composable from VeeValidate to create our form instance. We'll also add the Zod schema for validation.",[525,594,602],{"className":595,"code":596,"highlights":597,"language":549,"meta":580},[528],"\u003Cscript setup lang=\"ts\">\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { useForm, Field as VeeField } from 'vee-validate'\nimport * as 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\nconst { handleSubmit } = useForm({\n  validationSchema: toTypedSchema(formSchema),\n  initialValues: {\n    title: '',\n    description: '',\n  },\n})\n\nconst onSubmit = handleSubmit((values) => {\n  // Do something with the form values.\n  console.log(values)\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cform @submit=\"onSubmit\">\n    \u003C!-- Build the form here -->\n  \u003C/form>\n\u003C/template>\n",[546,547,548,598,599,600,601],20,21,22,23,[428,603,596],{"__ignoreMap":553},[558,605,607],{"id":606},"build-the-form","Build the form",[424,609,610,611,520,613,523],{},"We can now build the form using VeeValidate's ",[428,612,430],{},[428,614,430],{},[616,617],"component-source",{"name":467,"title":618},"Form.vue",[558,620,622],{"id":621},"done","Done",[424,624,625],{},"That's it. You now have a fully accessible form with client-side validation.",[424,627,628,629,632],{},"When you submit the form, the ",[428,630,631],{},"onSubmit"," function will be called with the validated form data. If the form data is invalid, VeeValidate will display the errors next to each field.",[443,634,636],{"id":635},"validation","Validation",[558,638,640],{"id":639},"client-side-validation","Client-side Validation",[424,642,643,644,647,648,650],{},"VeeValidate validates your form data using the Zod schema. Define a schema and pass it to the ",[428,645,646],{},"validationSchema"," option of the ",[428,649,491],{}," composable.",[525,652,657],{"className":653,"code":654,"highlights":655,"language":549,"meta":656},[528],"\u003Cscript setup lang=\"ts\">\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { useForm, Field as VeeField } from 'vee-validate'\nimport * as z from 'zod'\n\nconst formSchema = z.object({\n  title: z.string(),\n  description: z.string().optional(),\n})\n\nconst { handleSubmit } = useForm({\n  validationSchema: toTypedSchema(formSchema),\n  initialValues: {\n    title: '',\n    description: '',\n  },\n})\n\u003C/script>\n",[535,536,537,538,541],"showLineNumbers title=\"ExampleForm.vue\"",[428,658,654],{"__ignoreMap":553},[558,660,662],{"id":661},"validation-modes","Validation Modes",[424,664,665,666,668],{},"VeeValidate supports different validation strategies through the ",[428,667,194],{}," component props.",[525,670,674],{"className":671,"code":672,"highlights":673,"language":549,"meta":580},[528],"\u003CVeeField\n  v-slot=\"{ field, errors }\"\n  name=\"title\"\n  :validate-on-input=\"true\"\n>\n  \u003C!-- field content -->\n\u003C/VeeField>\n",[533],[428,675,672],{"__ignoreMap":553},[677,678,679,692],"table",{},[680,681,682],"thead",{},[683,684,685,689],"tr",{},[686,687,688],"th",{},"Prop",[686,690,691],{},"Description",[693,694,695,706,716,726],"tbody",{},[683,696,697,703],{},[698,699,700],"td",{},[428,701,702],{},"validateOnInput",[698,704,705],{},"Validation triggers on input event.",[683,707,708,713],{},[698,709,710],{},[428,711,712],{},"validateOnChange",[698,714,715],{},"Validation triggers on change event.",[683,717,718,723],{},[698,719,720],{},[428,721,722],{},"validateOnBlur",[698,724,725],{},"Validation triggers on blur event.",[683,727,728,733],{},[698,729,730],{},[428,731,732],{},"validateOnMount",[698,734,735],{},"Validation triggers when component is mounted.",[443,737,739],{"id":738},"displaying-errors","Displaying Errors",[424,741,742,743,746],{},"Display errors next to the field using ",[428,744,745],{},"\u003CFieldError />",". For styling and accessibility:",[483,748,749,759],{},[486,750,751,752,755,756,758],{},"Add the ",[428,753,754],{},":data-invalid"," prop to the shadcn-vue ",[428,757,430],{}," component.",[486,760,751,761,764,765,768,769,768,772,775],{},[428,762,763],{},":aria-invalid"," prop to the form control such as ",[428,766,767],{},"\u003CInput />",", ",[428,770,771],{},"\u003CSelectTrigger />",[428,773,774],{},"\u003CCheckbox />",", etc.",[525,777,781],{"className":778,"code":779,"highlights":780,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"email\">\n    \u003CField :data-invalid=\"!!errors.length\">\n      \u003CFieldLabel for=\"email\">\n        Email\n      \u003C/FieldLabel>\n      \u003CInput\n        id=\"email\"\n        v-bind=\"field\"\n        type=\"email\"\n        :aria-invalid=\"!!errors.length\"\n      />\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n",[531,538,540],[428,782,779],{"__ignoreMap":553},[443,784,786],{"id":785},"working-with-different-field-types","Working with Different Field Types",[558,788,206],{"id":789},"input",[483,791,792,799],{},[486,793,794,795,798],{},"For input fields, use ",[428,796,797],{},"v-bind=\"field\""," to bind VeeValidate's field object to the input.",[486,800,801,802,804,805,807,808,755,810,758],{},"To show errors, add the ",[428,803,763],{}," prop to the ",[428,806,767],{}," component and the ",[428,809,754],{},[428,811,430],{},[462,813],{":chromeLessOnMobile":453,"className":814,"name":815},[465,466],"VeeValidateInputDemo",[424,817,818,819,821],{},"For simple text inputs, use VeeValidate's ",[428,820,194],{}," component with scoped slots.",[525,823,827],{"className":824,"code":825,"highlights":826,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"name\">\n    \u003CField :data-invalid=\"!!errors.length\">\n      \u003CFieldLabel for=\"name\">\n        Name\n      \u003C/FieldLabel>\n      \u003CInput\n        id=\"name\"\n        v-bind=\"field\"\n        placeholder=\"Enter your name\"\n        :aria-invalid=\"!!errors.length\"\n      />\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n",[532,538,540],[428,828,825],{"__ignoreMap":553},[558,830,330],{"id":831},"textarea",[483,833,834,840],{},[486,835,836,837,839],{},"For textarea fields, use ",[428,838,797],{}," to bind VeeValidate's field object to the textarea.",[486,841,801,842,804,844,807,847,755,849,758],{},[428,843,763],{},[428,845,846],{},"\u003CTextarea />",[428,848,754],{},[428,850,430],{},[462,852],{":chromeLessOnMobile":453,"className":853,"name":854},[465,466],"VeeValidateTextareaDemo",[424,856,857,858,821],{},"For textarea fields, use VeeValidate's ",[428,859,194],{},[525,861,865],{"className":862,"code":863,"highlights":864,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"about\">\n    \u003CField :data-invalid=\"!!errors.length\">\n      \u003CFieldLabel for=\"about\">\n        More about you\n      \u003C/FieldLabel>\n      \u003CTextarea\n        id=\"about\"\n        v-bind=\"field\"\n        placeholder=\"I'm a software engineer...\"\n        class=\"min-h-[120px]\"\n        :aria-invalid=\"!!errors.length\"\n      />\n      \u003CFieldDescription>\n        Tell us more about yourself. This will be used to help us personalize your experience.\n      \u003C/FieldDescription>\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n",[532,541,545],[428,866,863],{"__ignoreMap":553},[558,868,278],{"id":869},"select",[483,871,872,883],{},[486,873,874,875,878,879,882],{},"For select components, use ",[428,876,877],{},"field.value"," and ",[428,880,881],{},"@update:model-value=\"field.onChange\""," for proper binding.",[486,884,801,885,804,887,807,889,755,891,758],{},[428,886,763],{},[428,888,771],{},[428,890,754],{},[428,892,430],{},[462,894],{":chromeLessOnMobile":453,"className":895,"name":898},[896,897],"sm:[&_.preview]:h-[500px]","sm:[&_pre]:!h-[500px]","VeeValidateSelectDemo",[525,900,904],{"className":901,"code":902,"highlights":903,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"language\">\n    \u003CField orientation=\"responsive\" :data-invalid=\"!!errors.length\">\n      \u003CFieldContent>\n        \u003CFieldLabel for=\"language\">\n          Spoken Language\n        \u003C/FieldLabel>\n        \u003CFieldDescription>For best results, select the language you speak.\u003C/FieldDescription>\n        \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n      \u003C/FieldContent>\n      \u003CSelect\n        :model-value=\"field.value\"\n        @update:model-value=\"field.onChange\"\n        @blur=\"field.onBlur\"\n      >\n        \u003CSelectTrigger\n          id=\"language\"\n          class=\"min-w-[120px]\"\n          :aria-invalid=\"!!errors.length\"\n        >\n          \u003CSelectValue placeholder=\"Select\" />\n        \u003C/SelectTrigger>\n        \u003CSelectContent position=\"item-aligned\">\n          \u003CSelectItem value=\"auto\">\n            Auto\n          \u003C/SelectItem>\n          \u003CSelectItem value=\"en\">\n            English\n          \u003C/SelectItem>\n        \u003C/SelectContent>\n      \u003C/Select>\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n",[532,538,548],[428,905,902],{"__ignoreMap":553},[558,907,150],{"id":908},"checkbox",[483,910,911,917,927],{},[486,912,913,914,916],{},"For checkbox arrays, use VeeValidate's ",[428,915,194],{}," component with a custom handler to manage array state.",[486,918,801,919,804,921,807,923,755,925,758],{},[428,920,763],{},[428,922,774],{},[428,924,754],{},[428,926,430],{},[486,928,929,930,933,934,937],{},"Remember to add ",[428,931,932],{},"data-slot=\"checkbox-group\""," to the ",[428,935,936],{},"\u003CFieldGroup />"," component for proper styling and spacing.",[462,939],{":chromeLessOnMobile":453,"className":940,"name":941},[465,466],"VeeValidateCheckboxDemo",[525,943,950],{"className":944,"code":945,"highlights":946,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"tasks\">\n    \u003CFieldSet>\n      \u003CFieldLegend variant=\"label\">\n        Tasks\n      \u003C/FieldLegend>\n      \u003CFieldDescription>Get notified when tasks you've created have updates.\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=\"!!errors.length\"\n        >\n          \u003CCheckbox\n            :id=\"`task-${task.id}`\"\n            :model-value=\"field.value?.includes(task.id) ?? false\"\n            :aria-invalid=\"!!errors.length\"\n            @update:model-value=\"(checked | 'indeterminate') => {\n              const currentTasks = field.value || []\n              const newValue = checked\n                ? [...currentTasks, task.id]\n                : currentTasks.filter(id => id !== task.id)\n              field.onChange(newValue)\n            }\"\n          />\n          \u003CFieldLabel :for=\"`task-${task.id}`\" class=\"font-normal\">\n            {{ task.label }}\n          \u003C/FieldLabel>\n        \u003C/Field>\n      \u003C/FieldGroup>\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/FieldSet>\n  \u003C/VeeField>\n\u003C/template>\n",[542,545,546,547,548,598,599,600,601,947,948,949],24,25,32,[428,951,945],{"__ignoreMap":553},[558,953,262],{"id":954},"radio-group",[483,956,957,964],{},[486,958,959,960,878,962,882],{},"For radio groups, use ",[428,961,877],{},[428,963,881],{},[486,965,801,966,804,968,807,971,755,973,758],{},[428,967,763],{},[428,969,970],{},"\u003CRadioGroupItem />",[428,972,754],{},[428,974,430],{},[462,976],{":chromeLessOnMobile":453,"className":977,"name":978},[465,466],"VeeValidateRadioGroupDemo",[525,980,985],{"className":981,"code":982,"highlights":983,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"plan\">\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        :model-value=\"field.value\"\n        @update:model-value=\"field.onChange\"\n      >\n        \u003CFieldLabel v-for=\"planOption in plans\" :key=\"planOption.id\" :for=\"`plan-${planOption.id}`\">\n          \u003CField orientation=\"horizontal\" :data-invalid=\"!!errors.length\">\n            \u003CFieldContent>\n              \u003CFieldTitle>{{ planOption.title }}\u003C/FieldTitle>\n              \u003CFieldDescription>{{ planOption.description }}\u003C/FieldDescription>\n            \u003C/FieldContent>\n            \u003CRadioGroupItem\n              :id=\"`plan-${planOption.id}`\"\n              :value=\"planOption.id\"\n              :aria-invalid=\"!!errors.length\"\n            />\n          \u003C/Field>\n        \u003C/FieldLabel>\n      \u003C/RadioGroup>\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/FieldSet>\n  \u003C/VeeField>\n\u003C/template>\n",[538,539,542,599,984],26,[428,986,982],{"__ignoreMap":553},[558,988,314],{"id":989},"switch",[483,991,992,1000],{},[486,993,994,995,878,998,882],{},"For switches, use ",[428,996,997],{},":model-value=\"field.value\"",[428,999,881],{},[486,1001,801,1002,804,1004,807,1007,755,1009,758],{},[428,1003,763],{},[428,1005,1006],{},"\u003CSwitch />",[428,1008,754],{},[428,1010,430],{},[462,1012],{":chromeLessOnMobile":453,"className":1013,"name":1014},[896,897],"VeeValidateSwitchDemo",[525,1016,1020],{"className":1017,"code":1018,"highlights":1019,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"twoFactor\">\n    \u003CField orientation=\"horizontal\" :data-invalid=\"!!errors.length\">\n      \u003CFieldContent>\n        \u003CFieldLabel for=\"two-factor\">\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=\"errors.length\" :errors=\"errors\" />\n      \u003C/FieldContent>\n      \u003CSwitch\n        id=\"two-factor\"\n        :model-value=\"field.value\"\n        :aria-invalid=\"!!errors.length\"\n        @update:model-value=\"field.onChange\"\n      />\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n",[532,540,544,545,546],[428,1021,1018],{"__ignoreMap":553},[558,1023,1025],{"id":1024},"complex-forms","Complex Forms",[424,1027,1028],{},"Here is an example of a more complex form with multiple fields and validation.",[462,1030],{":chromeLessOnMobile":453,"className":1031,"name":1034},[1032,1033],"sm:[&_.preview]:h-[1300px]","sm:[&_pre]:!h-[1300px]","VeeValidateComplexDemo",[443,1036,1038],{"id":1037},"resetting-the-form","Resetting the Form",[424,1040,1041,1042,1045,1046,1048],{},"Use the ",[428,1043,1044],{},"resetForm"," function returned by ",[428,1047,491],{}," to reset the form to its initial values.",[525,1050,1053],{"className":1051,"code":1052,"language":549,"meta":550},[528],"\u003Cscript setup lang=\"ts\">\nconst { handleSubmit, resetForm } = useForm({\n  validationSchema: formSchema,\n  // ...\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003CButton type=\"button\" variant=\"outline\" @click=\"resetForm\">\n    Reset\n  \u003C/Button>\n\u003C/template>\n",[428,1054,1052],{"__ignoreMap":553},[443,1056,1058],{"id":1057},"array-fields","Array Fields",[424,1060,1061,1062,1065],{},"VeeValidate provides a ",[428,1063,1064],{},"FieldArray"," component for managing dynamic array fields. This is useful when you need to add or remove fields dynamically.",[462,1067],{":chromeLessOnMobile":453,"className":1068,"name":1069},[465,466],"VeeValidateArrayDemo",[558,1071,1073],{"id":1072},"using-fieldarray","Using FieldArray",[424,1075,1041,1076,1078,1079,768,1082,1085,1086,1089],{},[428,1077,1064],{}," component to manage array fields. It provides ",[428,1080,1081],{},"fields",[428,1083,1084],{},"push",", and ",[428,1087,1088],{},"remove"," methods through its slot props.",[525,1091,1095],{"className":1092,"code":1093,"highlights":1094,"language":549,"meta":580},[528],"\u003Cscript setup lang=\"ts\">\nimport { FieldArray as VeeFieldArray } from 'vee-validate'\n\u003C/script>\n\n\u003Ctemplate>\n  \u003CVeeFieldArray v-slot=\"{ fields, push, remove }\" name=\"emails\">\n  \u003C!-- Array items go here -->\n  \u003C/VeeFieldArray>\n\u003C/template>\n",[535,536,537],[428,1096,1093],{"__ignoreMap":553},[558,1098,1100],{"id":1099},"array-field-structure","Array Field Structure",[424,1102,1103,1104,1107,1108,878,1111,481],{},"Wrap your array fields in a ",[428,1105,1106],{},"\u003CFieldSet />"," with a ",[428,1109,1110],{},"\u003CFieldLegend />",[428,1112,1113],{},"\u003CFieldDescription />",[525,1115,1118],{"className":1116,"code":1117,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CFieldSet class=\"gap-4\">\n    \u003CFieldLegend variant=\"label\">\n      Email Addresses\n    \u003C/FieldLegend>\n    \u003CFieldDescription>\n      Add up to 5 email addresses where we can contact you.\n    \u003C/FieldDescription>\n    \u003CFieldGroup class=\"gap-4\">\n      \u003C!-- Array items go here -->\n    \u003C/FieldGroup>\n  \u003C/FieldSet>\n\u003C/template>\n",[428,1119,1117],{"__ignoreMap":553},[558,1121,1123],{"id":1122},"field-pattern-for-array-items","Field Pattern for Array Items",[424,1125,1126,1127,1129,1130,481],{},"Map over the ",[428,1128,1081],{}," array and create fields for each item. ",[457,1131,1132,1133,1136],{},"Make sure to use ",[428,1134,1135],{},"field.key"," as the key",[525,1138,1141],{"className":1139,"code":1140,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CVeeFieldArray v-slot=\"{ fields, push, remove }\" name=\"emails\">\n    \u003CVeeField\n      v-for=\"(field, index) in fields\"\n      :key=\"field.key\"\n      v-slot=\"{ field: controllerField, errors }\"\n      :name=\"`emails[${index}].address`\"\n    >\n      \u003CField orientation=\"horizontal\" :data-invalid=\"!!errors.length\">\n        \u003CFieldContent class=\"flex-1\">\n          \u003CInputGroup>\n            \u003CInputGroupInput\n              :id=\"`email-${index}`\"\n              v-bind=\"controllerField\"\n              type=\"email\"\n              placeholder=\"name@example.com\"\n              autocomplete=\"email\"\n              :aria-invalid=\"!!errors.length\"\n            />\n            \u003C!-- Remove button -->\n          \u003C/InputGroup>\n          \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n        \u003C/FieldContent>\n      \u003C/Field>\n    \u003C/VeeField>\n  \u003C/VeeFieldArray>\n\u003C/template>\n",[428,1142,1140],{"__ignoreMap":553},[558,1144,1146],{"id":1145},"adding-items","Adding Items",[424,1148,1041,1149,1151],{},[428,1150,1084],{}," method to add new items to the array.",[525,1153,1156],{"className":1154,"code":1155,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CButton\n    type=\"button\"\n    variant=\"outline\"\n    size=\"sm\"\n    :disabled=\"fields.length >= 5\"\n    @click=\"push({ address: '' })\"\n  >\n    Add Email Address\n  \u003C/Button>\n\u003C/template>\n",[428,1157,1155],{"__ignoreMap":553},[558,1159,1161],{"id":1160},"removing-items","Removing Items",[424,1163,1041,1164,1166],{},[428,1165,1088],{}," method to remove items from the array. Add the remove button conditionally.",[525,1168,1171],{"className":1169,"code":1170,"language":549,"meta":580},[528],"\u003Ctemplate>\n  \u003CInputGroupAddon v-if=\"fields.length > 1\" align=\"inline-end\">\n    \u003CInputGroupButton\n      type=\"button\"\n      variant=\"ghost\"\n      size=\"icon-xs\"\n      :aria-label=\"`Remove email ${index + 1}`\"\n      @click=\"remove(index)\"\n    >\n      \u003CXIcon />\n    \u003C/InputGroupButton>\n  \u003C/InputGroupAddon>\n\u003C/template>\n",[428,1172,1170],{"__ignoreMap":553},[558,1174,1176],{"id":1175},"array-validation","Array Validation",[424,1178,1179,1180,1183],{},"Use Zod's ",[428,1181,1182],{},"array"," method to validate array fields.",[525,1185,1190],{"className":1186,"code":1188,"language":1189,"meta":580},[1187],"language-ts","const 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","ts",[428,1191,1188],{"__ignoreMap":553},{"title":553,"searchDepth":531,"depth":531,"links":1193},[1194,1195,1196,1197,1203,1207,1208,1217,1218],{"id":445,"depth":531,"text":446},{"id":470,"depth":531,"text":471},{"id":513,"depth":531,"text":514},{"id":556,"depth":531,"text":198,"children":1198},[1199,1200,1201,1202],{"id":560,"depth":532,"text":561},{"id":585,"depth":532,"text":586},{"id":606,"depth":532,"text":607},{"id":621,"depth":532,"text":622},{"id":635,"depth":531,"text":636,"children":1204},[1205,1206],{"id":639,"depth":532,"text":640},{"id":661,"depth":532,"text":662},{"id":738,"depth":531,"text":739},{"id":785,"depth":531,"text":786,"children":1209},[1210,1211,1212,1213,1214,1215,1216],{"id":789,"depth":532,"text":206},{"id":831,"depth":532,"text":330},{"id":869,"depth":532,"text":278},{"id":908,"depth":532,"text":150},{"id":954,"depth":532,"text":262},{"id":989,"depth":532,"text":314},{"id":1024,"depth":532,"text":1025},{"id":1037,"depth":531,"text":1038},{"id":1057,"depth":531,"text":1058,"children":1219},[1220,1221,1222,1223,1224,1225],{"id":1072,"depth":532,"text":1073},{"id":1099,"depth":532,"text":1100},{"id":1122,"depth":532,"text":1123},{"id":1145,"depth":532,"text":1146},{"id":1160,"depth":532,"text":1161},{"id":1175,"depth":532,"text":1176},"Build forms in Vue using VeeValidate and Zod.","md",{"doc":1229},"https://vee-validate.logaretm.com/v4",{},"---\ntitle: VeeValidate\ndescription: Build forms in Vue using VeeValidate and Zod.\nlinks:\n  doc: https://vee-validate.logaretm.com/v4\n---\n\nIn this guide, we will take a look at building forms with VeeValidate. We'll cover building forms with the `\u003CField />` component, adding schema validation using Zod, error handling, accessibility, and more.\n\n::vue-school-link{class=\"mt-6\" lesson=\"forms-and-form-validation-with-shadcn-vue\" placement=\"top\"}\nWatch a Vue School video about building forms and validation with shadcn-vue.\n::\n\n## Demo\n\nWe are going to build 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 VeeValidate. It is recommended to add basic browser validation in your production code.\n::\n\n::component-preview\n---\nname: VeeValidateDemo\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n## Approach\n\nThis form leverages VeeValidate for performant, flexible form handling. We'll build our form using the `\u003CField />` component, which gives you **complete flexibility over the markup and styling**.\n\n- Uses VeeValidate's `useForm` composable for form state management.\n- VeeValidate's `\u003CField />` component with scoped slots for controlled inputs with validation.\n- shadcn-vue `\u003CField />` components for building accessible forms.\n- Client-side validation using Zod with `toTypedSchema`.\n\n## Anatomy\n\nHere's a basic example of a form using VeeValidate's `\u003CField />` component with scoped slots and shadcn-vue `\u003CField />` components.\n\n```vue showLineNumbers {2-19}\n\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"title\">\n    \u003CField :data-invalid=\"!!errors.length\">\n      \u003CFieldLabel for=\"title\">\n        Bug Title\n      \u003C/FieldLabel>\n      \u003CInput\n        id=\"title\"\n        v-bind=\"field\"\n        placeholder=\"Login button not working on mobile\"\n        autocomplete=\"off\"\n        :aria-invalid=\"!!errors.length\"\n      />\n      \u003CFieldDescription>\n        Provide a concise title for your bug report.\n      \u003C/FieldDescription>\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/Field>\n  \u003C/VeeField>\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::callout\n---\nicon: true\n---\n**Note:** This example uses `zod v3` for schema validation, but you can replace it with any other Standard Schema validation library supported by VeeValidate.\n::\n\n```vue showLineNumbers title=\"Form.vue\"\n\u003Cscript setup lang=\"ts\">\nimport * as 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\nNext, we'll use the `useForm` composable from VeeValidate to create our form instance. We'll also add the Zod schema for validation.\n\n```vue showLineNumbers title=\"Form.vue\" {17-23}\n\u003Cscript setup lang=\"ts\">\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { useForm, Field as VeeField } from 'vee-validate'\nimport * as 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\nconst { handleSubmit } = useForm({\n  validationSchema: toTypedSchema(formSchema),\n  initialValues: {\n    title: '',\n    description: '',\n  },\n})\n\nconst onSubmit = handleSubmit((values) => {\n  // Do something with the form values.\n  console.log(values)\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cform @submit=\"onSubmit\">\n    \u003C!-- Build the form here -->\n  \u003C/form>\n\u003C/template>\n```\n\n### Build the form\n\nWe can now build the form using VeeValidate's `\u003CField />` component with scoped slots and shadcn-vue `\u003CField />` components.\n\n\u003C!-- This should use mdc component source -->\n::component-source{name=\"VeeValidateDemo\" title=\"Form.vue\"}\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, VeeValidate will display the errors next to each field.\n\n## Validation\n\n### Client-side Validation\n\nVeeValidate validates your form data using the Zod schema. Define a schema and pass it to the `validationSchema` option of the `useForm` composable.\n\n```vue showLineNumbers title=\"ExampleForm.vue\" {6-9,12}\n\u003Cscript setup lang=\"ts\">\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { useForm, Field as VeeField } from 'vee-validate'\nimport * as z from 'zod'\n\nconst formSchema = z.object({\n  title: z.string(),\n  description: z.string().optional(),\n})\n\nconst { handleSubmit } = useForm({\n  validationSchema: toTypedSchema(formSchema),\n  initialValues: {\n    title: '',\n    description: '',\n  },\n})\n\u003C/script>\n```\n\n### Validation Modes\n\nVeeValidate supports different validation strategies through the `Field` component props.\n\n```vue showLineNumbers title=\"Form.vue\" {4}\n\u003CVeeField\n  v-slot=\"{ field, errors }\"\n  name=\"title\"\n  :validate-on-input=\"true\"\n>\n  \u003C!-- field content -->\n\u003C/VeeField>\n```\n\n| Prop                    | Description                                              |\n| ----------------------- | -------------------------------------------------------- |\n| `validateOnInput`       | Validation triggers on input event.                     |\n| `validateOnChange`      | Validation triggers on change event.                    |\n| `validateOnBlur`        | Validation triggers on blur event.                      |\n| `validateOnMount`       | Validation triggers when component is mounted.          |\n\n## Displaying Errors\n\nDisplay errors next to the field using `\u003CFieldError />`. For styling and accessibility:\n\n- Add the `:data-invalid` prop to the shadcn-vue `\u003CField />` component.\n- Add the `:aria-invalid` prop to the form control such as `\u003CInput />`, `\u003CSelectTrigger />`, `\u003CCheckbox />`, etc.\n\n```vue showLineNumbers title=\"Form.vue\" {2,9,11}\n\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"email\">\n    \u003CField :data-invalid=\"!!errors.length\">\n      \u003CFieldLabel for=\"email\">\n        Email\n      \u003C/FieldLabel>\n      \u003CInput\n        id=\"email\"\n        v-bind=\"field\"\n        type=\"email\"\n        :aria-invalid=\"!!errors.length\"\n      />\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n```\n\n## Working with Different Field Types\n\n### Input\n\n- For input fields, use `v-bind=\"field\"` to bind VeeValidate's field object to the input.\n- To show errors, add the `:aria-invalid` prop to the `\u003CInput />` component and the `:data-invalid` prop to the shadcn-vue `\u003CField />` component.\n\n::component-preview\n---\nname: VeeValidateInputDemo\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\nFor simple text inputs, use VeeValidate's `Field` component with scoped slots.\n\n```vue showLineNumbers title=\"Form.vue\" {3,9,11}\n\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"name\">\n    \u003CField :data-invalid=\"!!errors.length\">\n      \u003CFieldLabel for=\"name\">\n        Name\n      \u003C/FieldLabel>\n      \u003CInput\n        id=\"name\"\n        v-bind=\"field\"\n        placeholder=\"Enter your name\"\n        :aria-invalid=\"!!errors.length\"\n      />\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n```\n\n### Textarea\n\n- For textarea fields, use `v-bind=\"field\"` to bind VeeValidate's field object to the textarea.\n- To show errors, add the `:aria-invalid` prop to the `\u003CTextarea />` component and the `:data-invalid` prop to the shadcn-vue `\u003CField />` component.\n\n::component-preview\n---\nname: VeeValidateTextareaDemo\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\nFor textarea fields, use VeeValidate's `Field` component with scoped slots.\n\n```vue showLineNumbers title=\"Form.vue\" {3,12,16}\n\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"about\">\n    \u003CField :data-invalid=\"!!errors.length\">\n      \u003CFieldLabel for=\"about\">\n        More about you\n      \u003C/FieldLabel>\n      \u003CTextarea\n        id=\"about\"\n        v-bind=\"field\"\n        placeholder=\"I'm a software engineer...\"\n        class=\"min-h-[120px]\"\n        :aria-invalid=\"!!errors.length\"\n      />\n      \u003CFieldDescription>\n        Tell us more about yourself. This will be used to help us personalize your experience.\n      \u003C/FieldDescription>\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n```\n\n### Select\n\n- For select components, use `field.value` and `@update:model-value=\"field.onChange\"` for proper binding.\n- To show errors, add the `:aria-invalid` prop to the `\u003CSelectTrigger />` component and the `:data-invalid` prop to the shadcn-vue `\u003CField />` component.\n\n::component-preview\n---\nname: VeeValidateSelectDemo\nclass: sm:[&_.preview]:h-[500px] sm:[&_pre]:!h-[500px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers title=\"Form.vue\" {3,9,19}\n\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"language\">\n    \u003CField orientation=\"responsive\" :data-invalid=\"!!errors.length\">\n      \u003CFieldContent>\n        \u003CFieldLabel for=\"language\">\n          Spoken Language\n        \u003C/FieldLabel>\n        \u003CFieldDescription>For best results, select the language you speak.\u003C/FieldDescription>\n        \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n      \u003C/FieldContent>\n      \u003CSelect\n        :model-value=\"field.value\"\n        @update:model-value=\"field.onChange\"\n        @blur=\"field.onBlur\"\n      >\n        \u003CSelectTrigger\n          id=\"language\"\n          class=\"min-w-[120px]\"\n          :aria-invalid=\"!!errors.length\"\n        >\n          \u003CSelectValue placeholder=\"Select\" />\n        \u003C/SelectTrigger>\n        \u003CSelectContent position=\"item-aligned\">\n          \u003CSelectItem value=\"auto\">\n            Auto\n          \u003C/SelectItem>\n          \u003CSelectItem value=\"en\">\n            English\n          \u003C/SelectItem>\n        \u003C/SelectContent>\n      \u003C/Select>\n    \u003C/Field>\n  \u003C/VeeField>\n\u003C/template>\n```\n\n### Checkbox\n\n- For checkbox arrays, use VeeValidate's `Field` component with a custom handler to manage array state.\n- To show errors, add the `:aria-invalid` prop to the `\u003CCheckbox />` component and the `:data-invalid` prop to the shadcn-vue `\u003CField />` component.\n- Remember to add `data-slot=\"checkbox-group\"` to the `\u003CFieldGroup />` component for proper styling and spacing.\n\n::component-preview\n---\nname: VeeValidateCheckboxDemo\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers title=\"Form.vue\" {13,16-25,32}\n\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"tasks\">\n    \u003CFieldSet>\n      \u003CFieldLegend variant=\"label\">\n        Tasks\n      \u003C/FieldLegend>\n      \u003CFieldDescription>Get notified when tasks you've created have updates.\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=\"!!errors.length\"\n        >\n          \u003CCheckbox\n            :id=\"`task-${task.id}`\"\n            :model-value=\"field.value?.includes(task.id) ?? false\"\n            :aria-invalid=\"!!errors.length\"\n            @update:model-value=\"(checked | 'indeterminate') => {\n              const currentTasks = field.value || []\n              const newValue = checked\n                ? [...currentTasks, task.id]\n                : currentTasks.filter(id => id !== task.id)\n              field.onChange(newValue)\n            }\"\n          />\n          \u003CFieldLabel :for=\"`task-${task.id}`\" class=\"font-normal\">\n            {{ task.label }}\n          \u003C/FieldLabel>\n        \u003C/Field>\n      \u003C/FieldGroup>\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/FieldSet>\n  \u003C/VeeField>\n\u003C/template>\n```\n\n### Radio Group\n\n- For radio groups, use `field.value` and `@update:model-value=\"field.onChange\"` for proper binding.\n- To show errors, add the `:aria-invalid` prop to the `\u003CRadioGroupItem />` component and the `:data-invalid` prop to the shadcn-vue `\u003CField />` component.\n\n::component-preview\n---\nname: VeeValidateRadioGroupDemo\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers title=\"Form.vue\" {9-10,13,21,26}\n\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"plan\">\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        :model-value=\"field.value\"\n        @update:model-value=\"field.onChange\"\n      >\n        \u003CFieldLabel v-for=\"planOption in plans\" :key=\"planOption.id\" :for=\"`plan-${planOption.id}`\">\n          \u003CField orientation=\"horizontal\" :data-invalid=\"!!errors.length\">\n            \u003CFieldContent>\n              \u003CFieldTitle>{{ planOption.title }}\u003C/FieldTitle>\n              \u003CFieldDescription>{{ planOption.description }}\u003C/FieldDescription>\n            \u003C/FieldContent>\n            \u003CRadioGroupItem\n              :id=\"`plan-${planOption.id}`\"\n              :value=\"planOption.id\"\n              :aria-invalid=\"!!errors.length\"\n            />\n          \u003C/Field>\n        \u003C/FieldLabel>\n      \u003C/RadioGroup>\n      \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n    \u003C/FieldSet>\n  \u003C/VeeField>\n\u003C/template>\n```\n\n### Switch\n\n- For switches, use `:model-value=\"field.value\"` and `@update:model-value=\"field.onChange\"` for proper binding.\n- To show errors, add the `:aria-invalid` prop to the `\u003CSwitch />` component and the `:data-invalid` prop to the shadcn-vue `\u003CField />` component.\n\n::component-preview\n---\nname: VeeValidateSwitchDemo\nclass: sm:[&_.preview]:h-[500px] sm:[&_pre]:!h-[500px]\nchromeLessOnMobile: true\n---\n::\n\n```vue showLineNumbers title=\"Form.vue\" {3,11,15-17}\n\u003Ctemplate>\n  \u003CVeeField v-slot=\"{ field, errors }\" name=\"twoFactor\">\n    \u003CField orientation=\"horizontal\" :data-invalid=\"!!errors.length\">\n      \u003CFieldContent>\n        \u003CFieldLabel for=\"two-factor\">\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=\"errors.length\" :errors=\"errors\" />\n      \u003C/FieldContent>\n      \u003CSwitch\n        id=\"two-factor\"\n        :model-value=\"field.value\"\n        :aria-invalid=\"!!errors.length\"\n        @update:model-value=\"field.onChange\"\n      />\n    \u003C/Field>\n  \u003C/VeeField>\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: VeeValidateComplexDemo\nclass: sm:[&_.preview]:h-[1300px] sm:[&_pre]:!h-[1300px]\nchromeLessOnMobile: true\n---\n::\n\n## Resetting the Form\n\nUse the `resetForm` function returned by `useForm` to reset the form to its initial values.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nconst { handleSubmit, resetForm } = useForm({\n  validationSchema: formSchema,\n  // ...\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003CButton type=\"button\" variant=\"outline\" @click=\"resetForm\">\n    Reset\n  \u003C/Button>\n\u003C/template>\n```\n\n## Array Fields\n\nVeeValidate provides a `FieldArray` component for managing dynamic array fields. This is useful when you need to add or remove fields dynamically.\n\n::component-preview\n---\nname: VeeValidateArrayDemo\nclass: sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]\nchromeLessOnMobile: true\n---\n::\n\n### Using FieldArray\n\nUse the `FieldArray` component to manage array fields. It provides `fields`, `push`, and `remove` methods through its slot props.\n\n```vue showLineNumbers title=\"Form.vue\" {6-8}\n\u003Cscript setup lang=\"ts\">\nimport { FieldArray as VeeFieldArray } from 'vee-validate'\n\u003C/script>\n\n\u003Ctemplate>\n  \u003CVeeFieldArray v-slot=\"{ fields, push, remove }\" name=\"emails\">\n  \u003C!-- Array items go here -->\n  \u003C/VeeFieldArray>\n\u003C/template>\n```\n\n### Array Field Structure\n\nWrap your array fields in a `\u003CFieldSet />` with a `\u003CFieldLegend />` and `\u003CFieldDescription />`.\n\n```vue showLineNumbers title=\"Form.vue\"\n\u003Ctemplate>\n  \u003CFieldSet class=\"gap-4\">\n    \u003CFieldLegend variant=\"label\">\n      Email Addresses\n    \u003C/FieldLegend>\n    \u003CFieldDescription>\n      Add up to 5 email addresses where we can contact you.\n    \u003C/FieldDescription>\n    \u003CFieldGroup class=\"gap-4\">\n      \u003C!-- Array items go here -->\n    \u003C/FieldGroup>\n  \u003C/FieldSet>\n\u003C/template>\n```\n\n### Field Pattern for Array Items\n\nMap over the `fields` array and create fields for each item. **Make sure to use `field.key` as the key**.\n\n```vue showLineNumbers title=\"Form.vue\"\n\u003Ctemplate>\n  \u003CVeeFieldArray v-slot=\"{ fields, push, remove }\" name=\"emails\">\n    \u003CVeeField\n      v-for=\"(field, index) in fields\"\n      :key=\"field.key\"\n      v-slot=\"{ field: controllerField, errors }\"\n      :name=\"`emails[${index}].address`\"\n    >\n      \u003CField orientation=\"horizontal\" :data-invalid=\"!!errors.length\">\n        \u003CFieldContent class=\"flex-1\">\n          \u003CInputGroup>\n            \u003CInputGroupInput\n              :id=\"`email-${index}`\"\n              v-bind=\"controllerField\"\n              type=\"email\"\n              placeholder=\"name@example.com\"\n              autocomplete=\"email\"\n              :aria-invalid=\"!!errors.length\"\n            />\n            \u003C!-- Remove button -->\n          \u003C/InputGroup>\n          \u003CFieldError v-if=\"errors.length\" :errors=\"errors\" />\n        \u003C/FieldContent>\n      \u003C/Field>\n    \u003C/VeeField>\n  \u003C/VeeFieldArray>\n\u003C/template>\n```\n\n### Adding Items\n\nUse the `push` method to add new items to the array.\n\n```vue showLineNumbers title=\"Form.vue\"\n\u003Ctemplate>\n  \u003CButton\n    type=\"button\"\n    variant=\"outline\"\n    size=\"sm\"\n    :disabled=\"fields.length >= 5\"\n    @click=\"push({ address: '' })\"\n  >\n    Add Email Address\n  \u003C/Button>\n\u003C/template>\n```\n\n### Removing Items\n\nUse the `remove` method to remove items from the array. Add the remove button conditionally.\n\n```vue showLineNumbers title=\"Form.vue\"\n\u003Ctemplate>\n  \u003CInputGroupAddon v-if=\"fields.length > 1\" align=\"inline-end\">\n    \u003CInputGroupButton\n      type=\"button\"\n      variant=\"ghost\"\n      size=\"icon-xs\"\n      :aria-label=\"`Remove email ${index + 1}`\"\n      @click=\"remove(index)\"\n    >\n      \u003CXIcon />\n    \u003C/InputGroupButton>\n  \u003C/InputGroupAddon>\n\u003C/template>\n```\n\n### Array Validation\n\nUse Zod's `array` method to validate array fields.\n\n```ts showLineNumbers title=\"Form.vue\"\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```\n",{"title":372,"description":1226},"nEk71TU61Kj93UIoScsnVpx2jY9Nf7r_O5v0MpK0tJ8",[1235,1236],{"title":367,"path":368,"stem":369,"children":-1},{"title":376,"path":377,"stem":378,"children":-1},1775650001360]