[{"data":1,"prerenderedAt":548},["ShallowReactive",2],{"content-\u002Fdocs\u002Fgetting-started\u002Fwhy-attaform":3},{"id":4,"title":5,"body":6,"description":540,"extension":541,"meta":542,"metaRows":543,"navigation":241,"path":544,"seo":545,"source":543,"stem":546,"__hash__":547},"docs\u002Fdocs\u002Fgetting-started\u002Fwhy-attaform.md","Why Attaform",{"type":7,"value":8,"toc":531},"minimark",[9,13,20,23,26,77,80,85,88,143,146,150,153,313,322,339,343,353,396,399,403,437,441,452,456,459,479,483,527],[10,11,5],"h1",{"id":12},"why-attaform",[14,15,16],"blockquote",{},[17,18,19],"p",{},"Five marks of a great form library. Attaform's North star, top to bottom.",[17,21,22],{},"Forms look simple from the outside. Inside, they're a thicket of subtle details: blank-value tracking, persistence, sensitive-name protection, multi-tab sync, SSR, async validation, nested objects and discriminated unions, efficient DOM tracking, errors flowing from validators and your server into one reactive form API. A great library handles every one for you, without making you reach for the type plumbing or wire up the side-channels yourself.",[17,24,25],{},"These five convictions guide Attaform's design:",[27,28,29,42,48,65,71],"ol",{},[30,31,32,36,37,41],"li",{},[33,34,35],"strong",{},"Telepathically accurate type safety."," Types flow from your schema into every surface: values, errors, paths, write shapes, etc. We don't do ",[38,39,40],"code",{},"any",".",[30,43,44,47],{},[33,45,46],{},"A tiny, predictable API that just works."," Small surface, no surprises. If you can't learn the core API in five minutes, it's not good enough.",[30,49,50,53,54,57,58,61,62,64],{},[33,51,52],{},"Stays out of your markup."," One directive, ",[38,55,56],{},"v-register",", does the heavy lifting. Your ",[38,59,60],{},"\u003Cinput>"," stays a native ",[38,63,60],{},"; nothing sits between the DOM and the form.",[30,66,67,70],{},[33,68,69],{},"Vue conventions, end to end."," Composables, reactivity, directives: the shapes you already know.",[30,72,73,76],{},[33,74,75],{},"The schema drives everything."," Data shape, defaults, validation, errors, and types. All from one Zod schema.",[17,78,79],{},"Why? Because at the end of the day, Vue and Nuxt devs deserve nice things too.",[81,82,84],"h2",{"id":83},"one-source-of-truth-your-schema","One source of truth: your schema",[17,86,87],{},"Write a Zod schema. That's the source of truth for:",[89,90,91,100,118,124,134],"ul",{},[30,92,93,96,97,99],{},[33,94,95],{},"Types."," Every path, value, error, and write shape is inferred. No ",[38,98,40],{},", no manual generics, no reaching for the type plumbing whenever you add a field.",[30,101,102,105,106,109,110,113,114,117],{},[33,103,104],{},"Defaults."," Attaform reads the schema's slim shape (",[38,107,108],{},"''"," for strings, ",[38,111,112],{},"0"," for numbers, ",[38,115,116],{},"false"," for booleans) and uses it as the storage default. Override per field; don't repeat what the schema already says.",[30,119,120,123],{},[33,121,122],{},"Validation."," Refinements run synchronously by default; async refinements await before submit dispatches.",[30,125,126,129,130,133],{},[33,127,128],{},"Errors."," Each one lands at the offending path. ",[38,131,132],{},"form.errors.email"," is reactive end-to-end.",[30,135,136,139,140,41],{},[33,137,138],{},"Metadata."," Labels, descriptions, placeholders, and free-form meta attach to schema fields and surface on ",[38,141,142],{},"form.fields.\u003Cpath>",[17,144,145],{},"One schema in, full reactive surface out.",[81,147,149],{"id":148},"type-safe-end-to-end","Type-safe end to end",[17,151,152],{},"Every part of the public surface is typed against your schema:",[154,155,160],"pre",{"className":156,"code":157,"language":158,"meta":159,"style":159},"language-ts shiki shiki-themes github-light github-dark","const schema = z.object({\n  email: z.email(),\n  age: z.number().int().min(13),\n})\n\nconst form = useForm({ schema })\n\nform.setValue('age', 21) \u002F\u002F ok\nform.setValue('age', 'twenty-one') \u002F\u002F type error\n","ts","",[38,161,162,189,201,230,236,243,259,264,292],{"__ignoreMap":159},[163,164,167,171,175,178,182,186],"span",{"class":165,"line":166},"line",1,[163,168,170],{"class":169},"szBVR","const",[163,172,174],{"class":173},"sj4cs"," schema",[163,176,177],{"class":169}," =",[163,179,181],{"class":180},"sVt8B"," z.",[163,183,185],{"class":184},"sScJk","object",[163,187,188],{"class":180},"({\n",[163,190,192,195,198],{"class":165,"line":191},2,[163,193,194],{"class":180},"  email: z.",[163,196,197],{"class":184},"email",[163,199,200],{"class":180},"(),\n",[163,202,204,207,210,213,216,218,221,224,227],{"class":165,"line":203},3,[163,205,206],{"class":180},"  age: z.",[163,208,209],{"class":184},"number",[163,211,212],{"class":180},"().",[163,214,215],{"class":184},"int",[163,217,212],{"class":180},[163,219,220],{"class":184},"min",[163,222,223],{"class":180},"(",[163,225,226],{"class":173},"13",[163,228,229],{"class":180},"),\n",[163,231,233],{"class":165,"line":232},4,[163,234,235],{"class":180},"})\n",[163,237,239],{"class":165,"line":238},5,[163,240,242],{"emptyLinePlaceholder":241},true,"\n",[163,244,246,248,251,253,256],{"class":165,"line":245},6,[163,247,170],{"class":169},[163,249,250],{"class":173}," form",[163,252,177],{"class":169},[163,254,255],{"class":184}," useForm",[163,257,258],{"class":180},"({ schema })\n",[163,260,262],{"class":165,"line":261},7,[163,263,242],{"emptyLinePlaceholder":241},[163,265,267,270,273,275,279,282,285,288],{"class":165,"line":266},8,[163,268,269],{"class":180},"form.",[163,271,272],{"class":184},"setValue",[163,274,223],{"class":180},[163,276,278],{"class":277},"sZZnC","'age'",[163,280,281],{"class":180},", ",[163,283,284],{"class":173},"21",[163,286,287],{"class":180},") ",[163,289,291],{"class":290},"sJ8bj","\u002F\u002F ok\n",[163,293,295,297,299,301,303,305,308,310],{"class":165,"line":294},9,[163,296,269],{"class":180},[163,298,272],{"class":184},[163,300,223],{"class":180},[163,302,278],{"class":277},[163,304,281],{"class":180},[163,306,307],{"class":277},"'twenty-one'",[163,309,287],{"class":180},[163,311,312],{"class":290},"\u002F\u002F type error\n",[17,314,315,317,318,321],{},[38,316,142],{}," knows the exact set of paths in the schema. ",[38,319,320],{},"form.errors.\u003Cpath>"," is reactive, typed, narrowable.",[17,323,324,325,328,329,332,333,338],{},"The types follow the form through every state. While the user is typing, ",[38,326,327],{},"form.values"," is wide enough to hold whatever they have put there so far: incomplete fields, undecided discriminators, rehydrated half-filled draft state from yesterday's session. Inside ",[38,330,331],{},"handleSubmit",", the same data flows through the schema and emerges with literals narrowed, refinements honored, and discriminated unions discriminating. Wide where reality is wide, tight where the schema guarantees it. ",[334,335,337],"a",{"href":336},"\u002Fdocs\u002Freading-the-form\u002Ftype-safety","Type safety"," walks through the trade.",[81,340,342],{"id":341},"native-inputs-vue-directive","Native inputs, Vue directive",[17,344,345,347,348,61,350,352],{},[38,346,56],{}," is a Vue directive, not a wrapper component. Your ",[38,349,60],{},[38,351,60],{},"; there's no field-component overhead between the DOM and the form.",[154,354,358],{"className":355,"code":356,"language":357,"meta":159,"style":159},"language-vue shiki shiki-themes github-light github-dark","\u003Cinput v-register=\"form.register('email')\" \u002F>\n","vue",[38,359,360],{"__ignoreMap":159},[163,361,362,365,369,372,375,378,380,383,385,388,391,393],{"class":165,"line":166},[163,363,364],{"class":180},"\u003C",[163,366,368],{"class":367},"s9eBZ","input",[163,370,371],{"class":184}," v-register",[163,373,374],{"class":180},"=",[163,376,377],{"class":277},"\"",[163,379,269],{"class":180},[163,381,382],{"class":184},"register",[163,384,223],{"class":180},[163,386,387],{"class":277},"'email'",[163,389,390],{"class":180},")",[163,392,377],{"class":277},[163,394,395],{"class":180}," \u002F>\n",[17,397,398],{},"That's the whole binding. A11y attributes, value sync, focus state, blank tracking. All native.",[81,400,402],{"id":401},"live-layered-validation","Live, layered validation",[89,404,405,419,422,434],{},[30,406,407,408,281,411,414,415,418],{},"Per-field on ",[38,409,410],{},"change",[38,412,413],{},"blur",", or ",[38,416,417],{},"submit",". Your call, per form.",[30,420,421],{},"Sync refinements fire on the keystroke; async refinements await.",[30,423,424,425,428,429,433],{},"A form's ",[38,426,427],{},"meta.valid"," is ",[430,431,432],"em",{},"gated",". It only flips true after every active path has resolved at least one validation pass, including the async ones. No flash-of-valid window for users with a slow uniqueness check.",[30,435,436],{},"Server-side errors map back into the same reactive form API. The render surface is the same whether the error came from Zod or your API.",[81,438,440],{"id":439},"ssr-first-hydration-clean","SSR-first, hydration-clean",[17,442,443,444,447,448,451],{},"Forms render server-side and hydrate without a flash. Nuxt is zero-config; bare Vue 3 plus ",[38,445,446],{},"@vue\u002Fserver-renderer"," takes two one-liner helpers. The form your server rendered ",[430,449,450],{},"is"," the form your client picks up.",[81,453,455],{"id":454},"built-into-the-core","Built into the core",[17,457,458],{},"These ship with the core, typed and orchestrated as first-class features:",[89,460,461,464,467,470,473,476],{},[30,462,463],{},"Field arrays with stable keys and per-item validation.",[30,465,466],{},"Undo \u002F redo with bounded history, opt-in per form.",[30,468,469],{},"Persistence with per-field opt-in, local \u002F session \u002F IndexedDB \u002F custom backends, and sensitive-name guards out of the box.",[30,471,472],{},"Multi-tab sync over BroadcastChannel, secure-context gated.",[30,474,475],{},"Discriminated unions with variant memory across discriminator switches.",[30,477,478],{},"A DevTools panel that surfaces every form on the page: values, errors, history, persistence drafts.",[81,480,482],{"id":481},"where-to-next","Where to next",[89,484,485,492,502,510,517],{},[30,486,487,491],{},[334,488,490],{"href":489},"\u002Fdocs\u002Fgetting-started\u002Fquick-start","Quick start",": your first form, end-to-end.",[30,493,494,498,499,41],{},[334,495,497],{"href":496},"\u002Fdocs\u002Freading-the-form\u002Fthe-form","The form",": the full reactive surface returned by ",[38,500,501],{},"useForm",[30,503,504,506,507,509],{},[334,505,337],{"href":336},": wide while typing, tight inside ",[38,508,331],{},", by design.",[30,511,512,516],{},[334,513,515],{"href":514},"\u002Fdocs\u002Fvalidation\u002Fwhen-validation-runs","When validation runs",": the moment errors appear.",[30,518,519,526],{},[334,520,522,523,525],{"href":521},"\u002Fdocs\u002Fbinding-inputs\u002Fv-register","The ",[38,524,56],{}," directive",": how Attaform binds inputs.",[528,529,530],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":159,"searchDepth":191,"depth":191,"links":532},[533,534,535,536,537,538,539],{"id":83,"depth":191,"text":84},{"id":148,"depth":191,"text":149},{"id":341,"depth":191,"text":342},{"id":401,"depth":191,"text":402},{"id":439,"depth":191,"text":440},{"id":454,"depth":191,"text":455},{"id":481,"depth":191,"text":482},"The case for picking Attaform for Vue 3 forms. Type-safe end to end, schema-driven defaults, layered validation, SSR-clean hydration, and built-in persistence, history, and devtools.","md",{},null,"\u002Fdocs\u002Fgetting-started\u002Fwhy-attaform",{"title":5,"description":540},"docs\u002Fgetting-started\u002Fwhy-attaform","ljRpvQjPSDTz2n72isXYPp_ByBSI1sKaISE0B7lKfWI",1780949757135]