[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-vue-weekly-definemodel-v-model-contract":92,"related-vue-weekly-definemodel-v-model-contract":502,"code:vue:true:12pz2dk":578,"code:vue:true:hb2xx3":579,"code:vue:true:jk421p":580,"code:vue:true:1xevys6":581,"code:ts:true:1n3cctu":582,"code:vue:true:1ddiqxh":583,"code:vue:true:1raub77":584,"code:vue:true:lwkv7b":585,"code:vue:true:wxacae":586,"code:vue:true:16cjxw1":587,"comments-01a0af18-2d15-7347-b2f4-6cdfa2ce56fa":588},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,[45,49,53,57,61,65,69,73,77,81,84,88],{"slug":46,"name":47,"articles":48},"webdev","Webdev",124,{"slug":50,"name":51,"articles":52},"javascript","Javascript",103,{"slug":54,"name":55,"articles":56},"frontend","Frontend",79,{"slug":58,"name":59,"articles":60},"tutorial","Tutorial",48,{"slug":62,"name":63,"articles":64},"css","Css",40,{"slug":66,"name":67,"articles":68},"typescript","Typescript",18,{"slug":70,"name":71,"articles":72},"performance","Performance",16,{"slug":74,"name":75,"articles":76},"react","React",15,{"slug":78,"name":79,"articles":80},"browser","Browser",11,{"slug":82,"name":83,"articles":80},"node","Node",{"slug":85,"name":86,"articles":87},"html","Html",9,{"slug":89,"name":90,"articles":91},"accessibility","Accessibility",8,{"id":93,"slug":94,"title":95,"subtitle":96,"excerpt":97,"coverUrl":98,"locale":13,"readingMinutes":99,"publishedAt":100,"viewCount":101,"likeCount":19,"commentCount":19,"author":102,"vertical":107,"topic":108,"tags":111,"_count":116,"playground":118,"body":120,"bodyMd":464,"seo":465,"translationGroupId":467,"series":468,"podcastUrl":96,"verticalId":5,"thread":491,"assessments":493,"translations":498,"quiz":500},"01a0af18-2d15-7347-b2f4-6cdfa2ce56fa","vue-weekly-definemodel-v-model-contract","Vue defineModel: The v-model Contract, Explained",null,"Vue's defineModel macro replaces the modelValue prop and update:modelValue event with one ref. The exact v-model contract behind it, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-definemodel-v-model-contract.png",13,"2026-10-05T06:11:02.623Z",53,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},"vue","Vue",[112,113,114,115],{"slug":109,"name":110,"color":96},{"slug":50,"name":51,"color":96},{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"assessments":117},1,{"slug":94,"title":119},"Vue defineModel &amp; the v-model contract — interactive playground",{"blocks":121,"version":117},[122,126,131,134,143,146,149,152,163,166,169,172,177,180,183,187,190,193,196,199,205,208,211,214,218,222,225,228,231,236,239,242,245,249,253,256,259,262,266,270,273,276,279,282,285,288,291,294,297,300,303,306,309,317,320,324,327,331,334,338,341,345,348,352,355,358,361,364,367,370,373,413,417,420,428,431,434,437,440,446,449,452,455,458],{"id":123,"html":124,"type":125},"b1","\u003Cp>You&#39;ve written this a dozen times: declare a \u003Ccode>modelValue\u003C\u002Fcode> prop, declare an \u003Ccode>update:modelValue\u003C\u002Fcode> emit, wire a computed&#39;s \u003Ccode>get\u003C\u002Fcode>\u002F\u003Ccode>set\u003C\u002Fcode> between them, and hope you spelled the event name identically in both places. Misspell it — \u003Ccode>updata:modelValue\u003C\u002Fcode>, \u003Ccode>update:modelvalue\u003C\u002Fcode>, anything — and nothing errors; at most a dev-mode warning scrolls past. The parent&#39;s \u003Ccode>v-model\u003C\u002Fcode> just stops updating, and you&#39;re left bisecting a component that looks correct. Vue 3.4 replaced that whole dance with a single macro, \u003Ccode>defineModel\u003C\u002Fcode>. But it isn&#39;t new magic bolted onto \u003Ccode>v-model\u003C\u002Fcode> — it&#39;s Vue writing the same prop-and-event contract for you, and once you can see that contract, every edge case here becomes predictable instead of surprising.\u003C\u002Fp>","paragraph",{"id":127,"html":128,"text":129,"type":130,"level":31},"b2","What You&#39;ll Learn","What You'll Learn","heading",{"id":132,"html":133,"type":125},"b3","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":135,"type":136,"items":137,"ordered":18},"b4","list",[138,139,140,141,142],"Explain exactly what \u003Ccode>v-model\u003C\u002Fcode> on a component compiles down to","Replace the manual prop\u002Femit\u002Fcomputed pattern with \u003Ccode>defineModel\u003C\u002Fcode> correctly","Give a model a default value, make it required, and type it in TypeScript","Bind multiple independent named models on one component","Read the modifiers a parent attaches to \u003Ccode>v-model\u003C\u002Fcode> and transform values with \u003Ccode>get\u003C\u002Fcode>\u002F\u003Ccode>set\u003C\u002Fcode>",{"id":144,"html":145,"text":145,"type":130,"level":31},"b5","Who This Is For",{"id":147,"html":148,"type":125},"b6","\u003Cp>You&#39;ve built Vue 3 components with \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode>, used \u003Ccode>defineProps\u003C\u002Fcode> and \u003Ccode>defineEmits\u003C\u002Fcode>, and put \u003Ccode>v-model\u003C\u002Fcode> on a native \u003Ccode>&lt;input&gt;\u003C\u002Fcode> at least once. This article is about what happens when you put \u003Ccode>v-model\u003C\u002Fcode> on \u003Cem>your own\u003C\u002Fem> component. If the read\u002Fwrite tracking underneath \u003Ccode>ref\u003C\u002Fcode> and \u003Ccode>reactive\u003C\u002Fcode> is still fuzzy, \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-reactivity-explained-ref-vs-reactive-cheat-sheet-4nij\">Vue Reactivity: ref vs reactive\u003C\u002Fa> builds that foundation — useful background, not required reading.\u003C\u002Fp>",{"id":150,"html":151,"text":151,"type":130,"level":31},"b7","Table of Contents",{"id":153,"type":136,"items":154,"ordered":18},"b8",[155,156,157,158,159,160,161,162],"\u003Ca href=\"#the-problem-the-propemit-dance-by-hand\">The Problem: The Prop\u002FEmit Dance, By Hand\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-v-model-is-always-a-prop-and-an-event\">The Mental Model: v-model Is Always a Prop and an Event\u003C\u002Fa>","\u003Ca href=\"#building-it-up-how-definemodel-actually-works\">Building It Up: How defineModel Actually Works\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge Cases and Gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best Practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat Sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key Takeaways\u003C\u002Fa>",{"id":164,"html":165,"type":125},"b9","\u003Cp>This is written against \u003Cstrong>Vue 3.5.x\u003C\u002Fstrong> (verified 3.5.43, released September 2026). \u003Ccode>defineModel\u003C\u002Fcode> has been stable, no-flag-required syntax since Vue 3.4 — nothing here needs the experimental flag that 3.3 required, and none of it changes in the 3.6 release candidate currently in testing: the only \u003Ccode>defineModel\u003C\u002Fcode>\u002F\u003Ccode>useModel\u003C\u002Fcode>-related fix in 3.6 so far is a Vapor-mode-only regression fix, and this article doesn&#39;t use Vapor mode.\u003C\u002Fp>",{"id":167,"html":168,"text":168,"type":130,"level":31},"b10","The Problem: The Prop\u002FEmit Dance, By Hand",{"id":170,"html":171,"type":125},"b11","\u003Cp>Say you&#39;re building a \u003Ccode>CurrencyInput\u003C\u002Fcode> component that a parent binds with \u003Ccode>v-model\u003C\u002Fcode>. Before \u003Ccode>defineModel\u003C\u002Fcode>, the idiomatic way to do this was a \u003Ccode>computed\u003C\u002Fcode> with a \u003Ccode>get\u003C\u002Fcode> and a \u003Ccode>set\u003C\u002Fcode>:\u003C\u002Fp>",{"id":173,"code":174,"type":175,"language":109,"highlight":176},"b12","\u003C!-- CurrencyInput.vue — the way you had to write it before Vue 3.4 -->\n\u003Cscript setup>\nimport { computed } from 'vue'\n\nconst props = defineProps({\n  modelValue: { type: Number, default: 0 },\n})\nconst emit = defineEmits(['update:modelValue'])\n\n\u002F\u002F A prop can't be written to directly — Vue warns and the write is dropped.\n\u002F\u002F So you build a local, writable stand-in with a get\u002Fset computed.\nconst model = computed({\n  get: () => props.modelValue,\n  set: (value) => emit('update:modelValue', value),\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput\n    type=\"number\"\n    :value=\"model\"\n    @input=\"model = Number($event.target.value)\"\n  \u002F>\n\u003C\u002Ftemplate>","code",[],{"id":178,"html":179,"type":125},"b13","\u003Cp>This works, and it isn&#39;t wrong — but look at what it costs. The prop name (\u003Ccode>modelValue\u003C\u002Fcode>) and the event name (\u003Ccode>update:modelValue\u003C\u002Fcode>) have to match a convention exactly, in two separate declarations, with no compiler check tying them together. Get the emit name wrong in both places and \u003Ccode>v-model\u003C\u002Fcode> on the parent&#39;s side just does nothing: no warning, no error, no update (wrong in only one place, dev mode at least warns). Multiply this by every component with a two-way binding, and by every component that needs more than one — a \u003Ccode>firstName\u003C\u002Fcode>\u002F\u003Ccode>lastName\u003C\u002Fcode> pair, say — and you&#39;re hand-writing the same four-line skeleton over and over, each copy a fresh chance to typo an event name.\u003C\u002Fp>",{"id":181,"html":182,"type":125},"b14","\u003Cp>\u003Ccode>defineModel\u003C\u002Fcode> collapses that entire block into one line:\u003C\u002Fp>",{"id":184,"code":185,"type":175,"language":109,"highlight":186},"b15","\u003Cscript setup>\nconst model = defineModel({ default: 0 })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput type=\"number\" v-model.number=\"model\" \u002F>\n\u003C\u002Ftemplate>",[],{"id":188,"html":189,"type":125},"b16","\u003Cp>That&#39;s not a different feature — it&#39;s the same prop, the same event, and the same get\u002Fset relationship, generated for you from a single declaration.\u003C\u002Fp>",{"id":191,"html":192,"text":192,"type":130,"level":31},"b17","The Mental Model: v-model Is Always a Prop and an Event",{"id":194,"html":195,"type":125},"b18","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> whatever syntax you write on the outside, \u003Ccode>v-model\u003C\u002Fcode> on a component always compiles to exactly two things — a prop bound to the current value, and a listener for an event that writes a new value back up. \u003Ccode>v-model=&quot;x&quot;\u003C\u002Fcode> on \u003Ccode>&lt;MyInput&gt;\u003C\u002Fcode> is shorthand for \u003Ccode>:model-value=&quot;x&quot; @update:model-value=&quot;x = $event&quot;\u003C\u002Fcode>. That&#39;s the entire contract. Every version of &quot;two-way binding on a component&quot; Vue has ever shipped — the \u003Ccode>value\u003C\u002Fcode>\u002F\u003Ccode>input\u003C\u002Fcode> convention in Vue 2, the \u003Ccode>modelValue\u003C\u002Fcode>\u002F\u003Ccode>update:modelValue\u003C\u002Fcode> convention in early Vue 3, and \u003Ccode>defineModel\u003C\u002Fcode> today — is just a different amount of \u003Cem>automation\u003C\u002Fem> over that same pair.\u003C\u002Fp>",{"id":197,"html":198,"type":125},"b19","\u003Cp>\u003Ccode>defineModel()\u003C\u002Fcode> is a compiler macro (available only inside \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode>; outside it, the helper it compiles to — \u003Ccode>useModel(props, &#39;modelValue&#39;)\u003C\u002Fcode> — does the same job once you declare the prop and emit yourself) that, for one declaration, generates:\u003C\u002Fp>",{"id":200,"type":136,"items":201,"ordered":17},"b20",[202,203,204],"A prop (\u003Ccode>modelValue\u003C\u002Fcode> by default, or a name you choose) added to the component&#39;s props, exactly as if you&#39;d written it in \u003Ccode>defineProps\u003C\u002Fcode>.","An emit (\u003Ccode>update:modelValue\u003C\u002Fcode>) added to the component&#39;s emits, exactly as if you&#39;d written it in \u003Ccode>defineEmits\u003C\u002Fcode>.","A \u003Ccode>computed\u003C\u002Fcode>-like ref that reads the prop and, when written to, fires the emit — the same get\u002Fset relationship you&#39;d otherwise write by hand.",{"id":206,"html":207,"type":125},"b21","\u003Cp>The one behavior that isn&#39;t just &quot;the same thing, shorter&quot;: if the parent doesn&#39;t bind that prop at all, the ref defineModel returns doesn&#39;t stay \u003Ccode>undefined\u003C\u002Fcode> and read-only the way a plain prop would. It falls back to behaving like an ordinary local \u003Ccode>ref\u003C\u002Fcode>, seeded with whatever \u003Ccode>default\u003C\u002Fcode> you gave it, fully writable, with no parent to sync to. That fallback is what makes \u003Ccode>defineModel\u003C\u002Fcode> usable for genuinely optional two-way bindings — a component with a sensible built-in state that a parent can \u003Cem>optionally\u003C\u002Fem> take over.\u003C\u002Fp>",{"id":209,"html":210,"text":210,"type":130,"level":31},"b22","Building It Up: How defineModel Actually Works",{"id":212,"html":213,"text":213,"type":130,"level":43},"b23","The basic case",{"id":215,"code":216,"type":175,"language":109,"highlight":217},"b24","\u003C!-- CurrencyInput.vue -->\n\u003Cscript setup>\nconst model = defineModel({ default: 0 })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput type=\"number\" v-model.number=\"model\" \u002F>\n\u003C\u002Ftemplate>",[],{"id":219,"code":220,"type":175,"language":109,"highlight":221},"b25","\u003C!-- Parent.vue -->\n\u003Cscript setup>\nimport { ref } from 'vue'\nimport CurrencyInput from '.\u002FCurrencyInput.vue'\n\nconst price = ref(19.99)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CCurrencyInput v-model=\"price\" \u002F>\n\u003C\u002Ftemplate>",[],{"id":223,"html":224,"type":125},"b26","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>model\u003C\u002Fcode> inside \u003Ccode>CurrencyInput\u003C\u002Fcode> and \u003Ccode>price\u003C\u002Fcode> inside the parent are two different refs kept in sync by the prop\u002Femit pair defineModel wrote for you. Mutating \u003Ccode>model.value\u003C\u002Fcode> inside the child doesn&#39;t reach across the component boundary directly — it emits, and the parent&#39;s \u003Ccode>v-model\u003C\u002Fcode> catches that emit and writes \u003Ccode>price.value\u003C\u002Fcode> for you.\u003C\u002Fp>",{"id":226,"html":227,"text":227,"type":130,"level":43},"b27","Options: default, required, and type",{"id":229,"html":230,"type":125},"b28","\u003Cp>\u003Ccode>defineModel\u003C\u002Fcode> takes the same options a prop does, because under the hood it \u003Cem>is\u003C\u002Fem> one:\u003C\u002Fp>",{"id":232,"code":233,"type":175,"language":234,"highlight":235},"b29","\u002F\u002F A required model — TypeScript removes `undefined` from its type for you.\nconst model = defineModel\u003Cstring>({ required: true })\n\n\u002F\u002F An optional model with a default, typed explicitly.\nconst model = defineModel\u003Cstring | undefined>({ default: 'Untitled' })","ts",[],{"id":237,"html":238,"type":125},"b30","\u003Cp>Typing it explicitly matters: an untyped \u003Ccode>defineModel()\u003C\u002Fcode> in a \u003Ccode>.vue\u003C\u002Fcode> file with \u003Ccode>&lt;script setup lang=&quot;ts&quot;&gt;\u003C\u002Fcode> infers as loosely as an untyped prop would, so name the type rather than relying on inference from a call site that doesn&#39;t exist yet.\u003C\u002Fp>",{"id":240,"html":241,"text":241,"type":130,"level":43},"b31","Multiple, independent named models",{"id":243,"html":244,"type":125},"b32","\u003Cp>A component can expose more than one two-way binding by naming each \u003Ccode>defineModel\u003C\u002Fcode> call — this is the same \u003Ccode>v-model:propName\u003C\u002Fcode> syntax Vue has supported since named component v-models existed, now paired with the macro instead of hand-written props and emits:\u003C\u002Fp>",{"id":246,"code":247,"type":175,"language":109,"highlight":248},"b33","\u003C!-- NameFields.vue -->\n\u003Cscript setup>\nconst firstName = defineModel('firstName', { default: '' })\nconst lastName = defineModel('lastName', { default: '' })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput v-model=\"firstName\" placeholder=\"First name\" \u002F>\n  \u003Cinput v-model=\"lastName\" placeholder=\"Last name\" \u002F>\n\u003C\u002Ftemplate>",[],{"id":250,"code":251,"type":175,"language":109,"highlight":252},"b34","\u003Ctemplate>\n  \u003CNameFields v-model:first-name=\"first\" v-model:last-name=\"last\" \u002F>\n\u003C\u002Ftemplate>",[],{"id":254,"html":255,"type":125},"b35","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> each named \u003Ccode>defineModel\u003C\u002Fcode> call is its own independent prop\u002Femit pair. There&#39;s no shared state between \u003Ccode>firstName\u003C\u002Fcode> and \u003Ccode>lastName\u003C\u002Fcode> beyond what your component logic adds — they&#39;re two separate contracts, not one contract split in two.\u003C\u002Fp>",{"id":257,"html":258,"text":258,"type":130,"level":43},"b36","Modifiers, and transforming the value",{"id":260,"html":261,"type":125},"b37","\u003Cp>A parent can attach modifiers to any \u003Ccode>v-model\u003C\u002Fcode>, same as \u003Ccode>v-model.trim\u003C\u002Fcode> on a native input. Read them by destructuring the second value \u003Ccode>defineModel\u003C\u002Fcode> returns, and use \u003Ccode>get\u003C\u002Fcode>\u002F\u003Ccode>set\u003C\u002Fcode> to act on them:\u003C\u002Fp>",{"id":263,"code":264,"type":175,"language":109,"highlight":265},"b38","\u003C!-- TitleField.vue -->\n\u003Cscript setup>\nconst [model, modifiers] = defineModel({\n  set(value) {\n    return modifiers.capitalize && typeof value === 'string'\n      ? value.charAt(0).toUpperCase() + value.slice(1)\n      : value\n  },\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput v-model=\"model\" \u002F>\n\u003C\u002Ftemplate>",[],{"id":267,"code":268,"type":175,"language":109,"highlight":269},"b39","\u003Ctemplate>\n  \u003CTitleField v-model.capitalize=\"title\" \u002F>\n\u003C\u002Ftemplate>",[],{"id":271,"html":272,"type":125},"b40","\u003C!-- playground:start -->",{"id":274,"html":275,"text":275,"type":130,"level":31},"b41","🎮 Try it yourself",{"id":277,"html":278,"type":125},"b42","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-definemodel-v-model-contract\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":280,"html":281,"type":125},"b43","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":283,"html":284,"type":125},"b44","\u003C!-- playground:end -->",{"id":286,"html":287,"text":287,"type":130,"level":31},"b45","Edge Cases and Gotchas",{"id":289,"html":290,"type":125},"b46","\u003Cp>\u003Cstrong>Object and array defaults need a factory, not a literal.\u003C\u002Fstrong> This is the same rule as a regular prop default, and it&#39;s easy to forget because \u003Ccode>defineModel({ default: 0 })\u003C\u002Fcode> looks so much like a plain value assignment: \u003Ccode>default: []\u003C\u002Fcode> hands every instance without a bound value the \u003Cem>same\u003C\u002Fem> array, so mutating it in one unbound instance leaks into every other. Use \u003Ccode>default: () =&gt; []\u003C\u002Fcode> instead.\u003C\u002Fp>",{"id":292,"html":293,"type":125},"b47","\u003Cp>\u003Cstrong>No parent binding means a local ref, not \u003Ccode>undefined\u003C\u002Fcode>.\u003C\u002Fstrong> This is the behavior that makes \u003Ccode>defineModel\u003C\u002Fcode> genuinely different from a plain required-less prop, and it&#39;s also the one detail worth testing explicitly: a component you render without \u003Ccode>v-model\u003C\u002Fcode> at all should still work, driven entirely by its own default. Vue 3.4.1 extended the fallback to parents that pass the prop one-way, without an \u003Ccode>update:\u003C\u002Fcode> listener, so on 3.5 it&#39;s simply the documented behavior, not a caveat to work around.\u003C\u002Fp>",{"id":295,"html":296,"type":125},"b48","\u003Cp>\u003Cstrong>A \u003Ccode>default\u003C\u002Fcode> plus a parent bound to \u003Ccode>undefined\u003C\u002Fcode> desyncs.\u003C\u002Fstrong> With \u003Ccode>&lt;Child v-model=&quot;myRef&quot; \u002F&gt;\u003C\u002Fcode> and \u003Ccode>myRef = ref()\u003C\u002Fcode>, the child shows its \u003Ccode>default\u003C\u002Fcode> while the parent&#39;s \u003Ccode>myRef\u003C\u002Fcode> stays \u003Ccode>undefined\u003C\u002Fcode> until the child writes. The Vue docs call this out as a warning: if the parent binds the model, give its ref a real starting value rather than leaning on the child&#39;s \u003Ccode>default\u003C\u002Fcode>.\u003C\u002Fp>",{"id":298,"html":299,"type":125},"b49","\u003Cp>\u003Cstrong>Don&#39;t declare the same prop name in both \u003Ccode>defineProps\u003C\u002Fcode> and \u003Ccode>defineModel\u003C\u002Fcode>.\u003C\u002Fstrong> \u003Ccode>defineModel(&#39;title&#39;)\u003C\u002Fcode> already declares a \u003Ccode>title\u003C\u002Fcode> prop. List \u003Ccode>title\u003C\u002Fcode> in \u003Ccode>defineProps\u003C\u002Fcode> too and the compiler won&#39;t complain — it merges both, and defineModel&#39;s options silently replace yours for that key. Any prop that isn&#39;t part of a \u003Ccode>v-model\u003C\u002Fcode> binding still belongs in an ordinary \u003Ccode>defineProps\u003C\u002Fcode>.\u003C\u002Fp>",{"id":301,"html":302,"type":125},"b50","\u003Cp>\u003Cstrong>\u003Ccode>get\u003C\u002Fcode> runs on every read; \u003Ccode>set\u003C\u002Fcode> runs only when the component itself assigns \u003Ccode>model.value\u003C\u002Fcode>.\u003C\u002Fstrong> Values arriving from the parent never pass through \u003Ccode>set\u003C\u002Fcode>, and with no parent binding \u003Ccode>set\u003C\u002Fcode>&#39;s return value is only emitted — the local ref keeps the raw value. Keep both transforms pure and cheap — no side effects, no async work. They&#39;re formatting hooks, not lifecycle hooks.\u003C\u002Fp>",{"id":304,"html":305,"type":125},"b51","\u003Cp>\u003Cstrong>Modifiers on an unnamed model and a named model are separate namespaces.\u003C\u002Fstrong> \u003Ccode>v-model.trim\u003C\u002Fcode> sets modifiers for the default (\u003Ccode>modelValue\u003C\u002Fcode>) model; \u003Ccode>v-model:title.trim\u003C\u002Fcode> sets modifiers scoped to the \u003Ccode>title\u003C\u002Fcode> model only. Destructure the modifiers from the matching \u003Ccode>defineModel\u003C\u002Fcode> call, not a shared object.\u003C\u002Fp>",{"id":307,"html":308,"text":308,"type":130,"level":31},"b52","Best Practices",{"id":310,"type":136,"items":311,"ordered":18},"b53",[312,313,314,315,316],"\u003Cstrong>Reach for \u003Ccode>defineModel\u003C\u002Fcode> for anything inherently two-way\u003C\u002Fstrong> — form controls, toggles, a value the child both displays and edits. It&#39;s the direct replacement for the old computed get\u002Fset pattern, not a new category of API.","\u003Cstrong>Prefer named models over overloading one \u003Ccode>modelValue\u003C\u002Fcode>\u003C\u002Fstrong> the moment a component genuinely exposes more than one independent piece of editable state. Two named models are clearer than one object-shaped model the child has to destructure.","\u003Cstrong>Type every \u003Ccode>defineModel\u003C\u002Fcode> explicitly in TypeScript.\u003C\u002Fstrong> An inferred type from a macro with no call-site arguments is easy to get wrong silently; write \u003Ccode>defineModel&lt;string&gt;()\u003C\u002Fcode> rather than leaning on inference.","\u003Cstrong>Don&#39;t use \u003Ccode>v-model\u003C\u002Fcode> to fake a read-only prop.\u003C\u002Fstrong> If the child never writes back, it&#39;s a normal prop, not a model — \u003Ccode>v-model\u003C\u002Fcode> signals &quot;this can flow both ways,&quot; and using it where that&#39;s untrue misleads the next person who reads the parent&#39;s template.","\u003Cstrong>Don&#39;t wire \u003Ccode>defineModel\u003C\u002Fcode> straight into a Pinia store.\u003C\u002Fstrong> \u003Ccode>v-model\u003C\u002Fcode> is a parent\u002Fchild contract; a store is global state. Write to store actions directly and keep the two mechanisms separate, or you&#39;ll end up debugging which one actually owns the value.",{"id":318,"html":319,"text":319,"type":130,"level":31},"b54","FAQ",{"id":321,"html":322,"text":323,"type":130,"level":43},"b55","Does \u003Ccode>defineModel\u003C\u002Fcode> replace \u003Ccode>defineProps\u003C\u002Fcode> entirely?","Does defineModel replace defineProps entirely?",{"id":325,"html":326,"type":125},"b56","\u003Cp>No. It only replaces the declaration for props that participate in a \u003Ccode>v-model\u003C\u002Fcode> binding. Every other prop on the component still goes in an ordinary \u003Ccode>defineProps\u003C\u002Fcode>.\u003C\u002Fp>",{"id":328,"html":329,"text":330,"type":130,"level":43},"b57","Can I use \u003Ccode>defineModel\u003C\u002Fcode> outside \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode>?","Can I use defineModel outside \u003Cscript setup>?",{"id":332,"html":333,"type":125},"b58","\u003Cp>Not the macro itself — it only exists inside \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode>. But the helper it compiles to, \u003Ccode>useModel()\u003C\u002Fcode> (3.4+), works in a plain \u003Ccode>setup()\u003C\u002Fcode>: declare the prop and \u003Ccode>update:\u003C\u002Fcode> emit yourself, then \u003Ccode>const model = useModel(props, &#39;modelValue&#39;)\u003C\u002Fcode>.\u003C\u002Fp>",{"id":335,"html":336,"text":337,"type":130,"level":43},"b59","Why does \u003Ccode>v-model\u003C\u002Fcode> on my component silently do nothing?","Why does v-model on my component silently do nothing?",{"id":339,"html":340,"type":125},"b60","\u003Cp>Before \u003Ccode>defineModel\u003C\u002Fcode>, this was almost always a mismatched prop\u002Femit name — \u003Ccode>modelValue\u003C\u002Fcode> declared in props but \u003Ccode>update:modalValue\u003C\u002Fcode> (typo) in the emit, or vice versa. \u003Ccode>defineModel\u003C\u002Fcode> removes that failure mode entirely, since one declaration generates both sides. If it&#39;s still not updating, check that the parent binds the name you declared (\u003Ccode>v-model:title\u003C\u002Fcode> for \u003Ccode>defineModel(&#39;title&#39;)\u003C\u002Fcode>), and that you haven&#39;t re-declared the prop in \u003Ccode>defineProps\u003C\u002Fcode> — that compiles silently and defineModel&#39;s options replace yours.\u003C\u002Fp>",{"id":342,"html":343,"text":344,"type":130,"level":43},"b61","Do I need to add the model&#39;s event to \u003Ccode>defineEmits\u003C\u002Fcode> as well?","Do I need to add the model's event to defineEmits as well?",{"id":346,"html":347,"type":125},"b62","\u003Cp>No — \u003Ccode>defineModel\u003C\u002Fcode> declares the prop and the emit for you. Adding the same event again in \u003Ccode>defineEmits\u003C\u002Fcode> is redundant but harmless — the compiler merges both lists.\u003C\u002Fp>",{"id":349,"html":350,"text":351,"type":130,"level":43},"b63","Is \u003Ccode>defineModel\u003C\u002Fcode> available in Vue 2?","Is defineModel available in Vue 2?",{"id":353,"html":354,"type":125},"b64","\u003Cp>No. Vue 2 has no compiler macro for this; components there declare \u003Ccode>value\u003C\u002Fcode>\u002F\u003Ccode>input\u003C\u002Fcode> (Vue 2&#39;s default model event, before the \u003Ccode>modelValue\u003C\u002Fcode>\u002F\u003Ccode>update:modelValue\u003C\u002Fcode> convention Vue 3 introduced) by hand, and that pattern doesn&#39;t change in the Vue 2 line. Treat any Vue 2 example that mentions \u003Ccode>defineModel\u003C\u002Fcode> as a mistake, not a version difference.\u003C\u002Fp>",{"id":356,"html":357,"type":125},"b65","\u003C!-- quiz:start -->",{"id":359,"html":360,"text":360,"type":130,"level":31},"b66","🧠 Test yourself",{"id":362,"html":363,"type":125},"b67","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-definemodel-v-model-contract\u002Fquiz\">Take the 9-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":365,"html":366,"type":125},"b68","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":368,"html":369,"type":125},"b69","\u003C!-- quiz:end -->",{"id":371,"html":372,"text":372,"type":130,"level":31},"b70","Cheat Sheet",{"id":374,"head":375,"rows":379,"type":412},"b71",[376,377,378],"Task","Code","Notes",[380,384,388,392,396,400,404,408],[381,382,383],"Basic model","\u003Ccode>const model = defineModel()\u003C\u002Fcode>","Prop \u003Ccode>modelValue\u003C\u002Fcode> + emit \u003Ccode>update:modelValue\u003C\u002Fcode>, generated",[385,386,387],"With default","\u003Ccode>defineModel({ default: 0 })\u003C\u002Fcode>","Object\u002Farray defaults need a factory: \u003Ccode>default: () =&gt; []\u003C\u002Fcode>",[389,390,391],"Required","\u003Ccode>defineModel&lt;string&gt;({ required: true })\u003C\u002Fcode>","Removes \u003Ccode>undefined\u003C\u002Fcode> from the TS type",[393,394,395],"Named model","\u003Ccode>defineModel(&#39;title&#39;)\u003C\u002Fcode>","Parent binds with \u003Ccode>v-model:title\u003C\u002Fcode>",[397,398,399],"Multiple models","\u003Ccode>defineModel(&#39;a&#39;)\u003C\u002Fcode> + \u003Ccode>defineModel(&#39;b&#39;)\u003C\u002Fcode>","Fully independent prop\u002Femit pairs",[401,402,403],"Read modifiers","\u003Ccode>const [m, mods] = defineModel()\u003C\u002Fcode>","\u003Ccode>mods.yourModifier\u003C\u002Fcode> is \u003Ccode>true\u003C\u002Fcode> when the parent adds \u003Ccode>.yourModifier\u003C\u002Fcode>",[405,406,407],"Transform value","\u003Ccode>defineModel({ get(v) {…}, set(v) { return … } })\u003C\u002Fcode>","\u003Ccode>set\u003C\u002Fcode> runs on the child&#39;s writes (its result is what&#39;s emitted); \u003Ccode>get\u003C\u002Fcode> runs on every read",[409,410,411],"No parent binding","(default behavior)","Ref falls back to a local, fully writable ref seeded by \u003Ccode>default\u003C\u002Fcode>","table",{"id":414,"code":415,"type":175,"language":109,"highlight":416},"b72","\u003Cscript setup lang=\"ts\">\n\u002F\u002F The full pattern: a required, typed, transformed model with modifiers.\nconst [model, modifiers] = defineModel\u003Cstring>({\n  required: true,\n  set(value) {\n    return modifiers.trim ? value.trim() : value\n  },\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput v-model.trim=\"model\" \u002F>\n\u003C\u002Ftemplate>",[],{"id":418,"html":419,"text":419,"type":130,"level":31},"b73","Key Takeaways",{"id":421,"type":136,"items":422,"ordered":18},"b74",[423,424,425,426,427],"\u003Ccode>v-model\u003C\u002Fcode> on a component is always a prop plus an event; \u003Ccode>defineModel\u003C\u002Fcode> doesn&#39;t change that contract, it generates it.","With no parent binding, \u003Ccode>defineModel\u003C\u002Fcode>&#39;s ref becomes a local, writable ref seeded by your \u003Ccode>default\u003C\u002Fcode> — not \u003Ccode>undefined\u003C\u002Fcode>.","Independent two-way bindings are named models (\u003Ccode>defineModel(&#39;name&#39;)\u003C\u002Fcode>), each its own prop\u002Femit pair.","Modifiers arrive as the second destructured value; transform the model with \u003Ccode>get\u003C\u002Fcode>\u002F\u003Ccode>set\u003C\u002Fcode>, not a watcher.","The same prop rules still apply underneath: factory functions for object\u002Farray defaults, explicit typing in TypeScript.",{"id":429,"html":430,"type":125},"b75","\u003Cp>The typo-prone emit dance from the top of this article is gone, but that was never the interesting part. \u003Ccode>v-model\u003C\u002Fcode> was never magic to begin with — it was always a prop and an event, wired by convention. \u003Ccode>defineModel\u003C\u002Fcode> just means you stop wiring it by hand, and everything you now know about that contract still applies exactly where the macro can&#39;t reach: get\u002Fset transforms, multiple named models, and the one moment it quietly falls back to a local ref.\u003C\u002Fp>",{"id":432,"html":433,"type":125},"b76","\u003Cp>What&#39;s the messiest custom \u003Ccode>v-model\u003C\u002Fcode> you&#39;ve had to debug — a typo&#39;d event name, a shared object default, or something else entirely? Drop it in the comments.\u003C\u002Fp>",{"id":435,"html":436,"type":125},"b77","\u003C!-- related:start -->",{"id":438,"html":439,"text":439,"type":130,"level":31},"b78","📚 Read next",{"id":441,"type":136,"items":442,"ordered":18},"b79",[443,444,445],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-composables-shared-state-trap\">Vue Composables: The Shared State Trap (+ Cheat Sheet)\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-nexttick-batching-dom-updates\">Vue nextTick Explained: How DOM Updates Are Batched\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive\">Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)\u003C\u002Fa>",{"id":447,"html":448,"type":125},"b80","\u003C!-- related:end -->",{"id":450,"type":451},"b81","divider",{"id":453,"html":454,"type":125},"b82","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":456,"html":457,"type":125},"b83","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":459,"type":136,"items":460,"ordered":18},"b84",[461,462,463],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","You've written this a dozen times: declare a `modelValue` prop, declare an `update:modelValue` emit, wire a computed's `get`\u002F`set` between them, and hope you spelled the event name identically in both places. Misspell it — `updata:modelValue`, `update:modelvalue`, anything — and nothing errors; at most a dev-mode warning scrolls past. The parent's `v-model` just stops updating, and you're left bisecting a component that looks correct. Vue 3.4 replaced that whole dance with a single macro, `defineModel`. But it isn't new magic bolted onto `v-model` — it's Vue writing the same prop-and-event contract for you, and once you can see that contract, every edge case here becomes predictable instead of surprising.\n\n## What You'll Learn\n\nBy the end of this article you'll be able to:\n\n- Explain exactly what `v-model` on a component compiles down to\n- Replace the manual prop\u002Femit\u002Fcomputed pattern with `defineModel` correctly\n- Give a model a default value, make it required, and type it in TypeScript\n- Bind multiple independent named models on one component\n- Read the modifiers a parent attaches to `v-model` and transform values with `get`\u002F`set`\n\n## Who This Is For\n\nYou've built Vue 3 components with `\u003Cscript setup>`, used `defineProps` and `defineEmits`, and put `v-model` on a native `\u003Cinput>` at least once. This article is about what happens when you put `v-model` on *your own* component. If the read\u002Fwrite tracking underneath `ref` and `reactive` is still fuzzy, [Vue Reactivity: ref vs reactive](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-reactivity-explained-ref-vs-reactive-cheat-sheet-4nij) builds that foundation — useful background, not required reading.\n\n## Table of Contents\n\n- [The Problem: The Prop\u002FEmit Dance, By Hand](#the-problem-the-propemit-dance-by-hand)\n- [The Mental Model: v-model Is Always a Prop and an Event](#the-mental-model-v-model-is-always-a-prop-and-an-event)\n- [Building It Up: How defineModel Actually Works](#building-it-up-how-definemodel-actually-works)\n- [Edge Cases and Gotchas](#edge-cases-and-gotchas)\n- [Best Practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat Sheet](#cheat-sheet)\n- [Key Takeaways](#key-takeaways)\n\nThis is written against **Vue 3.5.x** (verified 3.5.43, released September 2026). `defineModel` has been stable, no-flag-required syntax since Vue 3.4 — nothing here needs the experimental flag that 3.3 required, and none of it changes in the 3.6 release candidate currently in testing: the only `defineModel`\u002F`useModel`-related fix in 3.6 so far is a Vapor-mode-only regression fix, and this article doesn't use Vapor mode.\n\n## The Problem: The Prop\u002FEmit Dance, By Hand\n\nSay you're building a `CurrencyInput` component that a parent binds with `v-model`. Before `defineModel`, the idiomatic way to do this was a `computed` with a `get` and a `set`:\n\n```vue\n\u003C!-- CurrencyInput.vue — the way you had to write it before Vue 3.4 -->\n\u003Cscript setup>\nimport { computed } from 'vue'\n\nconst props = defineProps({\n  modelValue: { type: Number, default: 0 },\n})\nconst emit = defineEmits(['update:modelValue'])\n\n\u002F\u002F A prop can't be written to directly — Vue warns and the write is dropped.\n\u002F\u002F So you build a local, writable stand-in with a get\u002Fset computed.\nconst model = computed({\n  get: () => props.modelValue,\n  set: (value) => emit('update:modelValue', value),\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput\n    type=\"number\"\n    :value=\"model\"\n    @input=\"model = Number($event.target.value)\"\n  \u002F>\n\u003C\u002Ftemplate>\n```\n\nThis works, and it isn't wrong — but look at what it costs. The prop name (`modelValue`) and the event name (`update:modelValue`) have to match a convention exactly, in two separate declarations, with no compiler check tying them together. Get the emit name wrong in both places and `v-model` on the parent's side just does nothing: no warning, no error, no update (wrong in only one place, dev mode at least warns). Multiply this by every component with a two-way binding, and by every component that needs more than one — a `firstName`\u002F`lastName` pair, say — and you're hand-writing the same four-line skeleton over and over, each copy a fresh chance to typo an event name.\n\n`defineModel` collapses that entire block into one line:\n\n```vue\n\u003Cscript setup>\nconst model = defineModel({ default: 0 })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput type=\"number\" v-model.number=\"model\" \u002F>\n\u003C\u002Ftemplate>\n```\n\nThat's not a different feature — it's the same prop, the same event, and the same get\u002Fset relationship, generated for you from a single declaration.\n\n## The Mental Model: v-model Is Always a Prop and an Event\n\n**The mental model:** whatever syntax you write on the outside, `v-model` on a component always compiles to exactly two things — a prop bound to the current value, and a listener for an event that writes a new value back up. `v-model=\"x\"` on `\u003CMyInput>` is shorthand for `:model-value=\"x\" @update:model-value=\"x = $event\"`. That's the entire contract. Every version of \"two-way binding on a component\" Vue has ever shipped — the `value`\u002F`input` convention in Vue 2, the `modelValue`\u002F`update:modelValue` convention in early Vue 3, and `defineModel` today — is just a different amount of *automation* over that same pair.\n\n`defineModel()` is a compiler macro (available only inside `\u003Cscript setup>`; outside it, the helper it compiles to — `useModel(props, 'modelValue')` — does the same job once you declare the prop and emit yourself) that, for one declaration, generates:\n\n1. A prop (`modelValue` by default, or a name you choose) added to the component's props, exactly as if you'd written it in `defineProps`.\n2. An emit (`update:modelValue`) added to the component's emits, exactly as if you'd written it in `defineEmits`.\n3. A `computed`-like ref that reads the prop and, when written to, fires the emit — the same get\u002Fset relationship you'd otherwise write by hand.\n\nThe one behavior that isn't just \"the same thing, shorter\": if the parent doesn't bind that prop at all, the ref defineModel returns doesn't stay `undefined` and read-only the way a plain prop would. It falls back to behaving like an ordinary local `ref`, seeded with whatever `default` you gave it, fully writable, with no parent to sync to. That fallback is what makes `defineModel` usable for genuinely optional two-way bindings — a component with a sensible built-in state that a parent can *optionally* take over.\n\n## Building It Up: How defineModel Actually Works\n\n### The basic case\n\n```vue\n\u003C!-- CurrencyInput.vue -->\n\u003Cscript setup>\nconst model = defineModel({ default: 0 })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput type=\"number\" v-model.number=\"model\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n```vue\n\u003C!-- Parent.vue -->\n\u003Cscript setup>\nimport { ref } from 'vue'\nimport CurrencyInput from '.\u002FCurrencyInput.vue'\n\nconst price = ref(19.99)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CCurrencyInput v-model=\"price\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n**Key concept:** `model` inside `CurrencyInput` and `price` inside the parent are two different refs kept in sync by the prop\u002Femit pair defineModel wrote for you. Mutating `model.value` inside the child doesn't reach across the component boundary directly — it emits, and the parent's `v-model` catches that emit and writes `price.value` for you.\n\n### Options: default, required, and type\n\n`defineModel` takes the same options a prop does, because under the hood it *is* one:\n\n```ts\n\u002F\u002F A required model — TypeScript removes `undefined` from its type for you.\nconst model = defineModel\u003Cstring>({ required: true })\n\n\u002F\u002F An optional model with a default, typed explicitly.\nconst model = defineModel\u003Cstring | undefined>({ default: 'Untitled' })\n```\n\nTyping it explicitly matters: an untyped `defineModel()` in a `.vue` file with `\u003Cscript setup lang=\"ts\">` infers as loosely as an untyped prop would, so name the type rather than relying on inference from a call site that doesn't exist yet.\n\n### Multiple, independent named models\n\nA component can expose more than one two-way binding by naming each `defineModel` call — this is the same `v-model:propName` syntax Vue has supported since named component v-models existed, now paired with the macro instead of hand-written props and emits:\n\n```vue\n\u003C!-- NameFields.vue -->\n\u003Cscript setup>\nconst firstName = defineModel('firstName', { default: '' })\nconst lastName = defineModel('lastName', { default: '' })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput v-model=\"firstName\" placeholder=\"First name\" \u002F>\n  \u003Cinput v-model=\"lastName\" placeholder=\"Last name\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n```vue\n\u003Ctemplate>\n  \u003CNameFields v-model:first-name=\"first\" v-model:last-name=\"last\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n**Key concept:** each named `defineModel` call is its own independent prop\u002Femit pair. There's no shared state between `firstName` and `lastName` beyond what your component logic adds — they're two separate contracts, not one contract split in two.\n\n### Modifiers, and transforming the value\n\nA parent can attach modifiers to any `v-model`, same as `v-model.trim` on a native input. Read them by destructuring the second value `defineModel` returns, and use `get`\u002F`set` to act on them:\n\n```vue\n\u003C!-- TitleField.vue -->\n\u003Cscript setup>\nconst [model, modifiers] = defineModel({\n  set(value) {\n    return modifiers.capitalize && typeof value === 'string'\n      ? value.charAt(0).toUpperCase() + value.slice(1)\n      : value\n  },\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput v-model=\"model\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n```vue\n\u003Ctemplate>\n  \u003CTitleField v-model.capitalize=\"title\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-definemodel-v-model-contract\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Edge Cases and Gotchas\n\n**Object and array defaults need a factory, not a literal.** This is the same rule as a regular prop default, and it's easy to forget because `defineModel({ default: 0 })` looks so much like a plain value assignment: `default: []` hands every instance without a bound value the *same* array, so mutating it in one unbound instance leaks into every other. Use `default: () => []` instead.\n\n**No parent binding means a local ref, not `undefined`.** This is the behavior that makes `defineModel` genuinely different from a plain required-less prop, and it's also the one detail worth testing explicitly: a component you render without `v-model` at all should still work, driven entirely by its own default. Vue 3.4.1 extended the fallback to parents that pass the prop one-way, without an `update:` listener, so on 3.5 it's simply the documented behavior, not a caveat to work around.\n\n**A `default` plus a parent bound to `undefined` desyncs.** With `\u003CChild v-model=\"myRef\" \u002F>` and `myRef = ref()`, the child shows its `default` while the parent's `myRef` stays `undefined` until the child writes. The Vue docs call this out as a warning: if the parent binds the model, give its ref a real starting value rather than leaning on the child's `default`.\n\n**Don't declare the same prop name in both `defineProps` and `defineModel`.** `defineModel('title')` already declares a `title` prop. List `title` in `defineProps` too and the compiler won't complain — it merges both, and defineModel's options silently replace yours for that key. Any prop that isn't part of a `v-model` binding still belongs in an ordinary `defineProps`.\n\n**`get` runs on every read; `set` runs only when the component itself assigns `model.value`.** Values arriving from the parent never pass through `set`, and with no parent binding `set`'s return value is only emitted — the local ref keeps the raw value. Keep both transforms pure and cheap — no side effects, no async work. They're formatting hooks, not lifecycle hooks.\n\n**Modifiers on an unnamed model and a named model are separate namespaces.** `v-model.trim` sets modifiers for the default (`modelValue`) model; `v-model:title.trim` sets modifiers scoped to the `title` model only. Destructure the modifiers from the matching `defineModel` call, not a shared object.\n\n## Best Practices\n\n- **Reach for `defineModel` for anything inherently two-way** — form controls, toggles, a value the child both displays and edits. It's the direct replacement for the old computed get\u002Fset pattern, not a new category of API.\n- **Prefer named models over overloading one `modelValue`** the moment a component genuinely exposes more than one independent piece of editable state. Two named models are clearer than one object-shaped model the child has to destructure.\n- **Type every `defineModel` explicitly in TypeScript.** An inferred type from a macro with no call-site arguments is easy to get wrong silently; write `defineModel\u003Cstring>()` rather than leaning on inference.\n- **Don't use `v-model` to fake a read-only prop.** If the child never writes back, it's a normal prop, not a model — `v-model` signals \"this can flow both ways,\" and using it where that's untrue misleads the next person who reads the parent's template.\n- **Don't wire `defineModel` straight into a Pinia store.** `v-model` is a parent\u002Fchild contract; a store is global state. Write to store actions directly and keep the two mechanisms separate, or you'll end up debugging which one actually owns the value.\n\n## FAQ\n\n### Does `defineModel` replace `defineProps` entirely?\n\nNo. It only replaces the declaration for props that participate in a `v-model` binding. Every other prop on the component still goes in an ordinary `defineProps`.\n\n### Can I use `defineModel` outside `\u003Cscript setup>`?\n\nNot the macro itself — it only exists inside `\u003Cscript setup>`. But the helper it compiles to, `useModel()` (3.4+), works in a plain `setup()`: declare the prop and `update:` emit yourself, then `const model = useModel(props, 'modelValue')`.\n\n### Why does `v-model` on my component silently do nothing?\n\nBefore `defineModel`, this was almost always a mismatched prop\u002Femit name — `modelValue` declared in props but `update:modalValue` (typo) in the emit, or vice versa. `defineModel` removes that failure mode entirely, since one declaration generates both sides. If it's still not updating, check that the parent binds the name you declared (`v-model:title` for `defineModel('title')`), and that you haven't re-declared the prop in `defineProps` — that compiles silently and defineModel's options replace yours.\n\n### Do I need to add the model's event to `defineEmits` as well?\n\nNo — `defineModel` declares the prop and the emit for you. Adding the same event again in `defineEmits` is redundant but harmless — the compiler merges both lists.\n\n### Is `defineModel` available in Vue 2?\n\nNo. Vue 2 has no compiler macro for this; components there declare `value`\u002F`input` (Vue 2's default model event, before the `modelValue`\u002F`update:modelValue` convention Vue 3 introduced) by hand, and that pattern doesn't change in the Vue 2 line. Treat any Vue 2 example that mentions `defineModel` as a mistake, not a version difference.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 9-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-definemodel-v-model-contract\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## Cheat Sheet\n\n| Task | Code | Notes |\n| --- | --- | --- |\n| Basic model | `const model = defineModel()` | Prop `modelValue` + emit `update:modelValue`, generated |\n| With default | `defineModel({ default: 0 })` | Object\u002Farray defaults need a factory: `default: () => []` |\n| Required | `defineModel\u003Cstring>({ required: true })` | Removes `undefined` from the TS type |\n| Named model | `defineModel('title')` | Parent binds with `v-model:title` |\n| Multiple models | `defineModel('a')` + `defineModel('b')` | Fully independent prop\u002Femit pairs |\n| Read modifiers | `const [m, mods] = defineModel()` | `mods.yourModifier` is `true` when the parent adds `.yourModifier` |\n| Transform value | `defineModel({ get(v) {…}, set(v) { return … } })` | `set` runs on the child's writes (its result is what's emitted); `get` runs on every read |\n| No parent binding | (default behavior) | Ref falls back to a local, fully writable ref seeded by `default` |\n\n```vue\n\u003Cscript setup lang=\"ts\">\n\u002F\u002F The full pattern: a required, typed, transformed model with modifiers.\nconst [model, modifiers] = defineModel\u003Cstring>({\n  required: true,\n  set(value) {\n    return modifiers.trim ? value.trim() : value\n  },\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput v-model.trim=\"model\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n## Key Takeaways\n\n- `v-model` on a component is always a prop plus an event; `defineModel` doesn't change that contract, it generates it.\n- With no parent binding, `defineModel`'s ref becomes a local, writable ref seeded by your `default` — not `undefined`.\n- Independent two-way bindings are named models (`defineModel('name')`), each its own prop\u002Femit pair.\n- Modifiers arrive as the second destructured value; transform the model with `get`\u002F`set`, not a watcher.\n- The same prop rules still apply underneath: factory functions for object\u002Farray defaults, explicit typing in TypeScript.\n\nThe typo-prone emit dance from the top of this article is gone, but that was never the interesting part. `v-model` was never magic to begin with — it was always a prop and an event, wired by convention. `defineModel` just means you stop wiring it by hand, and everything you now know about that contract still applies exactly where the macro can't reach: get\u002Fset transforms, multiple named models, and the one moment it quietly falls back to a local ref.\n\nWhat's the messiest custom `v-model` you've had to debug — a typo'd event name, a shared object default, or something else entirely? Drop it in the comments.\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Vue Composables: The Shared State Trap (+ Cheat Sheet)](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-composables-shared-state-trap)\n- [Vue nextTick Explained: How DOM Updates Are Batched](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-nexttick-batching-dom-updates)\n- [Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":95,"canonical":466,"description":97},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-definemodel-v-model-contract","01a0af18-2d15-7347-b2f4-7005d0573ad4",{"name":469,"part":470,"total":470,"items":471},"Vue Deep Dive",5,[472,477,481,485,490],{"slug":473,"title":474,"publishedAt":475,"readingMinutes":476},"vue-weekly-reactivity-ref-vs-reactive","Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)","2026-08-24T13:57:38.080Z",14,{"slug":478,"title":479,"publishedAt":480,"readingMinutes":99},"vue-weekly-nexttick-batching-dom-updates","Vue nextTick Explained: How DOM Updates Are Batched","2026-09-14T12:19:00.606Z",{"slug":482,"title":483,"publishedAt":484,"readingMinutes":99},"vue-weekly-composables-shared-state-trap","Vue Composables: The Shared State Trap (+ Cheat Sheet)","2026-09-22T19:37:11.460Z",{"slug":486,"title":487,"publishedAt":488,"readingMinutes":489},"vue-weekly-computed-caching-invalidation","Vue computed(): What It Caches and When It Reruns","2026-09-30T12:10:53.362Z",12,{"slug":94,"title":95,"publishedAt":100,"readingMinutes":99},{"id":492,"locked":18},"01a0af18-2d88-7409-9ccb-d7453568482a",[494],{"id":495,"slug":94,"title":496,"_count":497},"01a0af18-2e09-770d-907e-a11709e4d355","Vue defineModel & the v-model Contract",{"questions":87},[499],{"locale":13,"slug":94},{"id":495,"slug":94,"title":496,"_count":501,"questionCount":87},{"questions":87},{"items":503,"meta":576},[504,516,531,546,561],{"id":93,"slug":94,"title":95,"subtitle":96,"excerpt":97,"coverUrl":98,"locale":13,"readingMinutes":99,"publishedAt":100,"viewCount":505,"likeCount":19,"commentCount":19,"author":506,"vertical":507,"topic":508,"tags":509,"_count":514,"playground":515,"hasQuiz":17,"hasPlayground":17},54,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[510,511,512,513],{"slug":109,"name":110,"color":96},{"slug":50,"name":51,"color":96},{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"assessments":117},{"slug":94},{"id":517,"slug":486,"title":487,"subtitle":96,"excerpt":518,"coverUrl":519,"locale":13,"readingMinutes":489,"publishedAt":488,"viewCount":520,"likeCount":19,"commentCount":19,"author":521,"vertical":522,"topic":523,"tags":524,"_count":529,"playground":530,"hasQuiz":17,"hasPlayground":17},"01a08af7-9d6f-74df-b76e-f6c445f54a47","Vue's computed() caches its result and only reruns when a tracked dependency changes, not on every read. The exact caching and invalidation model, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-computed-caching-invalidation.png",221,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[525,526,527,528],{"slug":109,"name":110,"color":96},{"slug":50,"name":51,"color":96},{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"assessments":117},{"slug":486},{"id":532,"slug":482,"title":483,"subtitle":96,"excerpt":533,"coverUrl":534,"locale":13,"readingMinutes":99,"publishedAt":484,"viewCount":535,"likeCount":19,"commentCount":19,"author":536,"vertical":537,"topic":538,"tags":539,"_count":544,"playground":545,"hasQuiz":17,"hasPlayground":17},"01a066ea-3098-75c9-b7e5-1d5426f773eb","Why two Vue components using the same composable can end up sharing state by accident, the mental model that explains it, and how to fix or embrace it.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-composables-shared-state-trap.png",386,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[540,541,542,543],{"slug":109,"name":110,"color":96},{"slug":50,"name":51,"color":96},{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"assessments":117},{"slug":482},{"id":547,"slug":478,"title":479,"subtitle":96,"excerpt":548,"coverUrl":549,"locale":13,"readingMinutes":99,"publishedAt":480,"viewCount":550,"likeCount":19,"commentCount":19,"author":551,"vertical":552,"topic":553,"tags":554,"_count":559,"playground":560,"hasQuiz":17,"hasPlayground":17},"01a04446-5a00-7624-b775-c73c6c035e67","How Vue batches reactive writes into one DOM update, why the DOM looks stale right after a change, and how nextTick and watch's flush option fix it.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-nexttick-batching-dom-updates.png",449,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[555,556,557,558],{"slug":109,"name":110,"color":96},{"slug":50,"name":51,"color":96},{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"assessments":117},{"slug":478},{"id":562,"slug":473,"title":474,"subtitle":96,"excerpt":563,"coverUrl":564,"locale":13,"readingMinutes":476,"publishedAt":475,"viewCount":565,"likeCount":19,"commentCount":117,"author":566,"vertical":567,"topic":568,"tags":569,"_count":574,"playground":575,"hasQuiz":17,"hasPlayground":17},"01a03410-2db8-735b-88b2-5285459dc915","A complete guide to Vue reactivity: how ref and reactive really work, why destructuring breaks updates, when to use each, and a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-reactivity-ref-vs-reactive.png",770,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[570,571,572,573],{"slug":109,"name":110,"color":96},{"slug":50,"name":51,"color":96},{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"assessments":117},{"slug":473},{"page":117,"perPage":577,"total":470,"totalPages":117},7,"\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003Cdiv>23\u003C\u002Fdiv>\u003Cdiv>24\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">&lt;!-- CurrencyInput.vue — the way you had to write it before Vue 3.4 --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-class\">setup\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> computed \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'vue'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> props \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineProps\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  modelValue\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> type\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Number\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> default\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> emit \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineEmits\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'update:modelValue'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F A prop can't be written to directly — Vue warns and the write is dropped.\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F So you build a local, writable stand-in with a get\u002Fset computed.\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> model \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">computed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  get\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> props\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>modelValue\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  set\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">emit\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'update:modelValue'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan>\n    \u003Cspan class=\"shj-class\">type\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"number\"\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">:value\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"model\"\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">@input\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"model = Number($event.target.value)\"\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-class\">setup\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> model \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineModel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> default\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan> \u003Cspan class=\"shj-class\">type\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"number\"\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model.number\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"model\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">&lt;!-- CurrencyInput.vue --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-class\">setup\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> model \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineModel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> default\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan> \u003Cspan class=\"shj-class\">type\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"number\"\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model.number\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"model\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">&lt;!-- Parent.vue --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-class\">setup\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> ref \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'vue'\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-class\">CurrencyInput\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'.\u002FCurrencyInput.vue'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> price \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">19.99\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">CurrencyInput\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"price\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F A required model — TypeScript removes `undefined` from its type for you.\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> model \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> defineModel\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> required\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F An optional model with a default, typed explicitly.\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> model \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> defineModel\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-num\">undefined\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> default\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Untitled'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">&lt;!-- NameFields.vue --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-class\">setup\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> firstName \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineModel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'firstName'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> default\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">''\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> lastName \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineModel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'lastName'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> default\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">''\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"firstName\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">placeholder\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"First name\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"lastName\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">placeholder\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"Last name\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">NameFields\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model:first-name\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"first\"\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model:last-name\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"last\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">&lt;!-- TitleField.vue --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-class\">setup\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>model\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> modifiers\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">defineModel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> modifiers\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>capitalize \u003Cspan class=\"shj-oper\">&#38;&#38;\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> value \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'string'\u003C\u002Fspan>\n      \u003Cspan class=\"shj-oper\">?\u003C\u002Fspan> value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">charAt\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toUpperCase\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">slice\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">1\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n      \u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> value\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"model\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">TitleField\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model.capitalize\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"title\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-class\">setup\u003C\u002Fspan> \u003Cspan class=\"shj-class\">lang\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"ts\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F The full pattern: a required, typed, transformed model with modifiers.\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>model\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> modifiers\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> defineModel\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  required\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> modifiers\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>trim \u003Cspan class=\"shj-oper\">?\u003C\u002Fspan> value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">trim\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> value\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">v-model.trim\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"model\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":589},[]]