How to Add v-model Support to Custom Vue Components

The v-model directive is one of the built-in directives in Vue.js. It creates two-way data binding between application state and form controls by combining property binding and event handling in a single syntax.

Without v-model, each control has to be connected manually. You would bind :value, or the appropriate attribute for that control, and listen for input or change events to write the new value back to your state. This approach works, but it quickly becomes repetitive in larger forms. A tutorial about using v-model for two-way binding describes it as syntactic sugar that combines these bindings into one directive.

With two-way data binding, changes in form controls automatically update application state, while changes in application state are reflected in the interface without manual DOM (Document Object Model) manipulation. Vue still preserves a clear data flow: the parent remains the source of truth, and the child informs the parent when a value needs to change instead of modifying props directly.

This article explains how v-model behaves on native elements and custom Vue 3 components, how the Vue 2 contract differs for migration, and how several implementation methods work, including defineModel(), manual modelValue wiring, computed setters, and local state synchronized with watchers. It also covers named v-model bindings, custom modifiers, a reusable validated input, and the same pattern applied to contenteditable editors.

Key Takeaways

  • v-model makes two-way data binding in Vue more concise by combining prop binding and event handling in one directive. It automatically keeps form controls and application state synchronized.
  • Vue maps different form controls to different property and event combinations internally. Text fields use value with input, while checkboxes use checked with change.
  • Vue 3 changed the default custom-component v-model contract to modelValue and update:modelValue. Vue 2 used value and input.
  • The defineModel() macro introduced in Vue 3.4 is the most concise way to add v-model support to custom components because it handles the prop and emit wiring automatically.
  • Custom Vue components should treat props as read-only and emit update events instead of changing props directly. This keeps Vue’s one-way data flow intact and helps prevent synchronization problems.
  • Vue supports multiple v-model implementation approaches, including explicit props and emits, computed setters, and watcher-based synchronization. The best choice depends on component complexity and the Vue version in use.
  • Vue 3 supports named bindings such as v-model:first-name, allowing one component to manage several two-way bindings. This replaces the older Vue 2 model option approach.
  • You can recreate v-model behavior for contenteditable elements by synchronizing DOM content and component state with refs, events, and watchers. This is a common pattern for WYSIWYG and rich-text editors.

Prerequisites

To follow this article, you should have:

  • A basic understanding of Vue components and props
  • Familiarity with JavaScript ES modules and functions
  • A Vue 3 development environment available locally or through an online playground

The examples use the Vue 3 Composition API and <script setup> syntax. Some sections also refer to Vue 2 behavior to clarify migration differences and older implementation techniques.

Understanding How v-model Works Internally

In HTML, input, select, and textarea are the main elements used to send user data into an application. On these native controls, v-model is a compile-time shortcut rather than hidden magic. The Vue forms guide documents the DOM property and event pair that Vue uses for each kind of control.

Vue chooses how to read and update data according to the element type:

Element type Property Event
<input> (text and most types), <textarea> value input
<input type="checkbox">, <input type="radio"> checked change
<select> value change

A text <input> can use v-model like this:

The template compiler expands that into code equivalent to the following:

<input :value="email" @input="email = $event.target.value" />

Vue applies the same principle to textarea, select, and the relevant input types. Radio buttons and checkboxes use checked and listen for change instead of pairing value with input.

When several checkboxes are bound to the same array or Set, or when a <select multiple> is used, v-model automatically collects the selected values into an array. The same behavior is described in discussions of checkbox binding and in the select section of the Vue forms guide.

One important default behavior is that v-model ignores initial value, checked, or selected attributes written directly on form elements in the markup. Vue treats JavaScript state, such as a ref initialized inside <script setup>, as the source of truth. The initial value should therefore be declared in the script instead of only in HTML.

The same contract also applies to custom components. A component that supports v-model must accept a prop containing the current value and emit an event when the parent should update that value. In Vue 3, components do not use value and input by default. Instead, they use modelValue and update:modelValue.

Vue 2 vs Vue 3 v-model Contracts

Vue 2 and Vue 3 use different default prop-and-event contracts for v-model.

Vue Version Prop Event
Vue 2 value input
Vue 3 modelValue update:modelValue

For components, Vue 3 compiles v-model into a modelValue prop and an update:modelValue event. This is the standard contract for custom inputs.

Vue 2 used a value prop together with an input event. When migrating an older project, this naming change is one of the most important differences to understand. The Vue 3 migration guide for v-model documents the complete set of breaking changes.

When <MyInput v-model="email" /> is used on a Vue 3 component, the compiler expands it into a prop binding and an update listener in a form similar to a native element:

<MyInput
  :model-value="email"
  @update:model-value="newValue => email = newValue"
/>

Your build may use the camelCase form modelValue in script. Both forms represent the same contract.

Adding v-model to Custom Components

For a custom component to support v-model, the child must receive the bound value through a prop and notify the parent when that value should change. In Vue 3, the default prop is modelValue, and the update event is update:modelValue. The child should treat modelValue as read-only and emit update:modelValue with the new value. It should never assign to the prop directly, because the parent needs to remain the single source of truth.

Recommended: defineModel() (Vue 3.4+)

The simplest option is the defineModel() macro in <script setup>. This convenience macro is expanded by the compiler into a modelValue prop synchronized with a local ref and an update:modelValue emit when the ref changes. The behavior is described in the Under the Hood section of the component v-model guide.

BasicInput.vue

<template>
  <input v-model="model" />
</template>

<script setup>
const model = defineModel()
</script>

Use the component like this:

<BasicInput v-model="email" />

The value returned by defineModel() is a ref:

  • Its .value stays synchronized with the value passed by the parent through v-model.
  • When the child changes model.value, Vue emits update:modelValue, which updates the parent state as well.

This is why placing v-model on a native control inside a wrapper component is so common: the parent still uses the simple v-model API on the custom component while the wrapper delegates the actual input work to a native <input>.

You can pass prop options to defineModel(), including required: true or a default value:

const model = defineModel({ required: true })
// or
const model = defineModel({ default: '' })

Use default carefully when the parent does not provide v-model. The official defineModel() documentation warns that the parent ref can remain undefined while the child initializes itself with a default, such as 1. That creates a mismatch between parent and child. This edge case should be documented or conflicting defaults should be avoided when the parent may omit the binding.

Manual Declaration (Any Vue 3 Version)

If defineModel() is unavailable, such as in Vue 3.3 or earlier, declare the prop and event explicitly. This is the approach shown for pre-3.4 usage.

BasicInput.vue

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

The parent’s <BasicInput v-model="email" /> is compiled to the same :modelValue and @update:modelValue wiring described in the component v-model guide. The underlying pattern is always the same: the child receives a prop and emits an event when the parent should update it.

In Vue 2, the corresponding implementation used a value prop and emitted an input event instead:

BasicInput.vue

<template>
  <input
    :value="value"
    @input="$emit('input', $event.target.value)"
  />
</template>

<script>
export default {
  props: ['value']
}
</script>

The child emits input because that is the event Vue 2 listens for when v-model is used on a custom component. Emitting another event name requires extra configuration through the Vue 2 model option, which is covered later.

When moving this kind of component to Vue 3, rename the prop to modelValue, change the emitted event to update:modelValue, and update the parent templates. Alternatively, use defineModel() so the compiler performs the wiring.

Comparing v-model Implementation Strategies

Vue provides several ways to implement v-model behavior in custom components. Each approach follows the same parent-facing contract, which is modelValue with update:modelValue by default or a corresponding named argument pair. The right option depends on the Vue version, component complexity, synchronization needs, and whether older projects must remain supported.

The component v-model guide covers defineModel(), manual props and emits, writable computed properties, and modifier handling. The following comparison shows where each pattern fits best.

Common v-model Implementation Patterns

Approach Best For Advantages Tradeoffs
defineModel() Modern Vue 3.4+ applications Minimal boilerplate, easy to read, officially recommended Requires Vue 3.4 or newer
Manual modelValue + update:modelValue Vue 3.0+ compatibility Explicit and flexible Requires more repetitive boilerplate
Computed getter/setter Wrapping or transforming values before emitting them Keeps transformation logic in one place Can be slightly harder for beginners to follow
Local refs with watchers Complex editors or asynchronous synchronization Useful when local state temporarily differs from parent state Adds more synchronization logic and moving parts

For most new Vue 3 applications using Vue 3.4 or later, defineModel() is the preferred approach because it removes repetitive prop and emit declarations while preserving the standard v-model contract.

Using a Computed Getter/Setter

Another common pattern uses a computed property with both a getter and a setter. The Vue documentation presents this as another way to implement v-model in a child component: the getter returns modelValue, and the setter emits update:modelValue. This is particularly useful when the child needs to transform or validate data before sending it back, such as trimming strings, coercing numbers, or normalizing user input.

BasicInput.vue

<template>
  <input v-model="value" />
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get: () => props.modelValue,
  set: (newValue) => {
    emit('update:modelValue', newValue.trim())
  }
})
</script>

In this example, the setter removes surrounding whitespace before synchronizing the value with the parent component. The same pattern could apply other rules, such as converting text to uppercase, removing invalid characters, or constraining a numeric value, without changing how the parent uses v-model.

Using Local State and Watchers

Some components, including rich-text editors, debounced search fields, and form builders, need temporary local state before updating the parent. In these cases, the parent should not necessarily change after every keystroke or DOM mutation. Instead, the child keeps a working copy and commits changes at the appropriate time.

When this pattern is necessary, a local ref can be synchronized with the parent through watch():

SearchInput.vue

<script setup>
import { ref, watch } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const localValue = ref(props.modelValue)

watch(
  () => props.modelValue,
  (newValue) => {
    localValue.value = newValue
  }
)

watch(localValue, (newValue) => {
  if (newValue !== props.modelValue) {
    emit('update:modelValue', newValue)
  }
})
</script>

This approach is better suited to advanced components where updates may be delayed, transformed, or synchronized with third-party libraries. One watcher pulls parent changes into localValue, while another watcher, or a debounced handler, pushes child changes back through emit('update:modelValue', ...). This structure helps avoid infinite update loops.

Vue 2 model Option for Legacy Projects

In Vue 2, components could customize v-model with the model option. This allowed the component to change both the prop name and the event name used by v-model, with one custom pairing available for each component.

For example, a checkbox component may use checked and change instead of the default value and input pair:

CustomCheckbox.vue

<template>
  <input
    type="checkbox"
    :checked="checked"
    @change="$emit('change', $event.target.checked)"
  />
</template>

<script>
export default {
  model: {
    prop: 'checked',
    event: 'change'
  },

  props: {
    checked: Boolean
  }
}
</script>

Parent usage:

<CustomCheckbox v-model="isEnabled" />

With this configuration, Vue maps v-model to the checked prop and listens for the change event automatically.

Vue 3 replaced this approach with named v-model arguments such as v-model:checked. These are clearer in templates and allow multiple two-way bindings on a single component without a separate model configuration object.

Named v-model and Multiple Bindings

The Vue 2 model option for customizing a prop and event does not exist in Vue 3. Instead, Vue 3 uses an argument on v-model. As described in the v-model arguments documentation, v-model:propName synchronizes with the propName prop and listens for update:propName.

This naming follows Vue’s component-event convention: the update: prefix is combined with the prop name. It also avoids conflicts with other props on the same component, which is useful when a field is actually named value or when a checkbox-style checked binding must exist alongside the default text-oriented modelValue.

With defineModel(), pass the argument name as the first parameter:

BasicInput.vue

<template>
  <input type="text" v-model="hidden" />
</template>

<script setup>
const hidden = defineModel('hidden')
</script>

Parent usage:

<BasicInput v-model:hidden="email" />

This compiles to a :hidden binding and an @update:hidden listener, avoiding collisions with other props or the default modelValue binding. In templates, kebab-case arguments such as v-model:first-name map to camelCase props such as firstName in script, following Vue’s normal prop-casing rules.

Several v-model bindings can be attached to the same component, as shown in the multiple v-model bindings documentation:

<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

UserName.vue

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

Optional prop settings can be passed as the second argument, for example defineModel('title', { required: true }).

Custom Modifiers on Component v-model

Native form controls support built-in modifiers such as .lazy, .number, and .trim, as described in the forms guide. Custom components can support modifiers as well. For example, v-model.capitalize="text" on the parent makes a capitalize flag available to the child. With defineModel(), you can destructure its result with const [model, modifiers] = defineModel() or use get and set options to transform values. Named arguments use a corresponding {arg}Modifiers prop, such as titleModifiers for v-model:title.capitalize. The Vue documentation on handling v-model modifiers provides complete examples.

Practical Example: Building a Reusable Input Component

The following example combines several of the concepts covered above into a reusable Vue 3 form component. It uses defineModel() for the field value, standard defineProps declarations for presentation and validation rules, and a computed property for derived error text. The parent keeps a simple v-model API while the child manages labels and validation messages.

This component:

  • Accepts a label prop
  • Supports v-model through defineModel()
  • Shows validation feedback
  • Keeps the parent state synchronized automatically

BaseInput.vue

<template>
  <label class="input-wrapper">
    <span>{{ label }}</span>

    <input
      v-model="model"
      :placeholder="placeholder"
      :class="{ invalid: errorMessage }"
    />

    <small v-if="errorMessage">
      {{ errorMessage }}
    </small>
  </label>
</template>

<script setup>
import { computed } from 'vue'

const model = defineModel()

const props = defineProps({
  label: {
    type: String,
    required: true
  },

  placeholder: {
    type: String,
    default: ''
  },

  minLength: {
    type: Number,
    default: 0
  }
})

const errorMessage = computed(() => {
  if (
    props.minLength &&
    (model.value ?? '').length < props.minLength
  ) {
    return `Input must be at least ${props.minLength} characters long.`
  }

  return ''
})
</script>

The component can be used from a parent like this:

<template>
  <BaseInput
    v-model="username"
    label="Username"
    placeholder="Enter your username"
    :minLength="5"
  />

  <p>Current value: {{ username }}</p>
</template>

<script setup>
import { ref } from 'vue'
import BaseInput from './BaseInput.vue'

const username = ref('')
</script>

This pattern fits reusable form systems because the component remains compatible with standard Vue v-model behavior while still supporting validation, formatting, and extra interface features. The validation in this example runs in the child only for display purposes. For rules that apply to an entire form, the component can be combined with a dedicated validation library or the browser’s constraint validation API, while the v-model contract on BaseInput remains unchanged for parent components.

Using v-model on contenteditable

A contenteditable element is a div or similar element that can be configured to behave like an input. Unlike <input> and <textarea>, it is not a form control, so Vue’s built-in v-model expansion is not applied automatically.

A contenteditable element is created by adding the contenteditable attribute:

<div
  class="editor"
  contenteditable="true"
  ref="editor"
></div>

contenteditable elements are often used for WYSIWYG editors because they are convenient to work with and broadly supported by modern browsers. MDN notes that the attribute accepts true, false, or the string "plaintext-only" for simpler editing without rich formatting.

Vue does not provide native v-model support for contenteditable elements in the same way it does for form controls. The same contract therefore needs to be implemented manually: read the element’s content on input, write parent state into the DOM when the model changes, and protect against feedback loops when both parent and child modify the same node.

Here is an example using defineModel() with a template ref:

ContentEditor.vue

<template>
  <div
    ref="editorRef"
    class="editor"
    contenteditable="true"
    @input="onInput"
  ></div>
</template>

<script setup>
import { ref, watch, onMounted } from 'vue'

const model = defineModel()
const editorRef = ref(null)

function onInput() {
  const el = editorRef.value
  if (!el) return
  model.value = el.innerText
}

onMounted(() => {
  const el = editorRef.value
  if (el && model.value != null) {
    el.innerText = model.value
  }
})

watch(model, (val) => {
  const el = editorRef.value
  if (!el || el.innerText === val) return
  el.innerText = val ?? ''
})
</script>

The parent can use <ContentEditor v-model="content" /> just like any other custom input.

A few practical details are important:

  • innerText vs innerHTML: innerText stores plain text and is usually the safer choice for simple editors. innerHTML retains markup but requires sanitization when the content may come from users in order to prevent XSS.
  • Avoiding loops: The watch checks el.innerText === val before writing, so an update from the child does not immediately cause an unnecessary DOM write.
  • Initial content: onMounted initializes the editor when the parent already contains a value, following the Vue forms guide rule that JavaScript state is the source of truth.

For production WYSIWYG editors, a dedicated library such as TipTap, Quill, or a similar tool is often wrapped in a component that still exposes v-model through the same defineModel() or update:modelValue pattern.

Common Mistakes and How to Avoid Them

Several implementation mistakes appear frequently when v-model is used with custom components, especially while moving between Vue 2 and Vue 3 patterns. Understanding these problems makes it easier to avoid synchronization bugs and confusing component behavior.

Mutating Props Directly

One of the most common mistakes is changing a prop directly inside a child component.

For example, the following code is incorrect:

<script setup>
const props = defineProps(['modelValue'])

function updateValue(newValue) {
  props.modelValue = newValue
}
</script>

Props are read-only in Vue. Changing them directly causes warnings because the parent component owns the source of truth.

Emit an update event instead, or use defineModel():

<script setup>
const emit = defineEmits(['update:modelValue'])

function updateValue(newValue) {
  emit('update:modelValue', newValue)
}
</script>

Or use defineModel():

const model = defineModel()

model.value = 'Updated value'

This preserves the correct direction of state changes while still providing two-way binding.

Using the Wrong Event Name in Vue 3

Another common migration error is continuing to emit the Vue 2 input event from Vue 3 components.

The following Vue 2 pattern does not work correctly with Vue 3’s default v-model behavior:

<input
  :value="modelValue"
  @input="$emit('input', $event.target.value)"
/>

In Vue 3, the correct event is update:modelValue:

<input
  :value="modelValue"
  @input="$emit('update:modelValue', $event.target.value)"
/>

When v-model is used on a custom component, Vue 3 specifically listens for the update:modelValue event.

Forgetting to Declare Emits

Vue 3 components should explicitly declare the events they emit.

For example:

defineEmits(['update:modelValue'])

Although some examples may still work without defineEmits(), leaving it out can result in:

  • Missing type inference
  • Less explicit component APIs
  • Runtime warnings in stricter configurations

Declaring emitted events also makes components easier to understand and maintain because other developers can immediately see which events the component supports.

Mixing Vue 2 and Vue 3 Patterns During Migration

Projects moving from Vue 2 to Vue 3 can accidentally combine conventions from both versions.

Examples include:

  • Using a value prop together with update:modelValue
  • Using modelValue while still emitting input
  • Combining the Vue 2 model option with Vue 3 named v-model arguments

These mismatched contracts normally cause v-model synchronization to stop working as expected.

To keep the implementation consistent:

  • Use value with input consistently in Vue 2
  • Use modelValue with update:modelValue consistently in Vue 3
  • Prefer defineModel() in modern Vue 3.4+ applications

Overusing Watchers for Simple Inputs

Watchers are valuable for advanced scenarios such as debounced inputs, rich-text editors, or third-party integrations. For normal form controls, however, they are often unnecessary.

The following pattern is generally more complicated than a basic input needs:

watch(localValue, (newValue) => {
  emit('update:modelValue', newValue)
})

For simple inputs, defineModel() or a computed setter is usually easier to maintain.

Watchers should be used when a component genuinely needs temporary local state or asynchronous synchronization behavior.

Forgetting About Default Value Synchronization

When a default option is passed to defineModel(), that default exists only on the child side. The parent’s bound ref does not automatically receive the same default when the parent leaves the ref uninitialized or omits v-model. The official defineModel() documentation points out that the child can display one value while the parent still contains undefined, which can be easy to overlook during testing.

For example, a model might use an empty string as its fallback value:

const model = defineModel({
  default: ''
})

If the parent uses <MyInput /> without v-model, or binds a ref that starts as undefined, the input can appear as an empty field while parentRef remains undefined. The interface looks correct, but any logic reading the parent ref does not see the child’s default.

In reusable component libraries, this difference can be reduced by:

  • Initializing the parent ref explicitly, for example with const email = ref('') before passing it through v-model
  • Documenting which defaults the child applies and when the parent needs to provide its own value
  • Avoiding conflicting defaults when both the parent and child define fallback values

Keeping defaults aligned on both sides helps prevent subtle synchronization bugs, particularly in larger forms that contain many wrapped inputs built on the same pattern.

FAQs

The following answers summarize common questions about custom-component v-model. They follow the Vue 3 component v-model documentation and the behavior described above.

1. How do I use v-model in a custom Vue component?

The component needs to accept a prop that contains the bound value and emit an update when that value should change. In Vue 2, the default prop is value and the event is input. In Vue 3, they are modelValue and update:modelValue. The parent can then write <MyInput v-model="email" />, and Vue expands it into the matching prop binding and update listener. This follows the same principle as native inputs but uses the prop and event names defined by the component contract. In Vue 3.4 and later, defineModel() can implement this contract instead of declaring the prop and emit manually. The macro still compiles to the same modelValue and update:modelValue interface.

2. How does v-model work differently in Vue 2 versus Vue 3?

In Vue 2, v-model on a component binds to a prop called value and listens for an input event by default. Vue 3 binds to modelValue and listens for update:modelValue, which matches Vue’s general prop-and-event naming conventions more closely. Customization also changed: Vue 2 used a component-level model option to choose a different prop and event, while Vue 3 uses named arguments such as v-model:title. These map to title and update:title without a separate configuration object.

3. What is defineModel() in Vue 3 and when should I use it?

defineModel() is a compiler macro introduced in Vue 3.4 for use inside <script setup>. It declares the modelValue prop and update:modelValue emit automatically and returns a ref that stays synchronized with the value bound by the parent. It is useful in Vue 3.4 and later when you want the most concise way to add default v-model support, especially for wrapper inputs, without repeating defineProps and defineEmits in every component. Standard prop settings such as required and default can still be supplied to defineModel(). For a named binding, pass the argument name first, such as defineModel('title').

4. Can a Vue 3 component support multiple v-model bindings?

Yes. One component instance can expose more than one two-way binding. In the parent, use v-model:propName="value" for each field, such as v-model:first-name and v-model:last-name. In the child, every argument corresponds to its own prop and update:propName event. With defineModel(), use calls such as defineModel('firstName') and defineModel('lastName'), or implement the same contract manually, so that each input remains synchronized with the correct parent state.

5. What is a vnode in Vue and how does it relate to v-model?

A vnode, or virtual node, is Vue’s lightweight description of a DOM element or component in the virtual DOM tree. When v-model is used, the compiler still converts the template into vnode trees with the correct props and event listeners attached. You do not implement v-model by creating vnodes yourself, but understanding that they exist helps explain how prop and event bindings remain intact across re-renders without manually changing the real DOM.

6. What is the v-model directive in Vue?

v-model is a built-in directive that connects application state with what the user sees through two-way binding. On native controls such as <input>, it is shorthand for binding :value, or the appropriate property such as checked for checkboxes, and listening for the input or change event that carries the new value. The Vue 3 forms guide documents the property-and-event combinations for form controls. On custom components, v-model is shorthand for binding the model prop, such as modelValue, and listening for the matching update event so the parent and child stay synchronized without manual event handlers in every parent template. Modifiers such as .trim and .number work with native inputs, and custom components can access modifiers through defineModel() or modelModifiers.

7. Why should I avoid mutating a prop directly instead of using v-model correctly?

Vue expects data to flow from the parent to the child through props. If a child directly assigns a new value to a prop, such as props.modelValue = 'new text', Vue warns at runtime and the parent’s source of truth is not updated reliably. The supported pattern is to treat the prop as read-only in the child and emit update:modelValue, or a named equivalent, so that the parent can change the bound ref or data. A correct v-model implementation follows this pattern whether it uses defineModel() or explicit emitted events.

8. How do I add v-model support to a custom checkbox component?

Checkboxes do not use the same default value or modelValue relationship as text inputs. Their selection state is represented through checked, and they normally trigger change. In Vue 2, configure the component’s model option with prop: 'checked' and event: 'change'. In Vue 3, use a named binding such as v-model:checked="isOn" in the parent and defineModel('checked') in the child, or declare a checked prop and an update:checked emit manually. Then bind the native checkbox’s checked property to that model and emit updates when change occurs.

Conclusion

You have seen how v-model expands on native elements and custom Vue 3 components, where the default contract uses modelValue and update:modelValue, while Vue 2 used value, input, and the model option. The same contract can be implemented with defineModel(), explicit props and emits, computed setters, or local state synchronized through watchers. It can also be extended through named v-model bindings, custom modifiers, and manual synchronization for contenteditable editors, as demonstrated by the reusable BaseInput example. In every case, the parent owns the data, the child emits updates, and a single v-model in the template keeps the public API straightforward.

For additional reading, consult the official documentation for component v-model, a tutorial covering v-model for native controls, a guide to adding two-way data binding to custom Vue components, and the Vue 3 v-model migration notes when maintaining Vue 2 code. Additional Vue.js resources can also provide more exercises and projects.

Source: digitalocean.com

Create a Free Account

Register now and get access to our Cloud Services.

Posts you might be interested in: