| |

Vue.js 32 🟢 Custom Two-Way Binding using Component v-model

Two-way binding is the pattern where a value flows from the parent to the child as a prop, and the child’s changes flow back to the parent as an event. In Vue, the v-model directive is the syntactic sugar for this pattern. On a native input, v-model="name" expands to :value="name" and @input="name = $event.target.value". On a component, the same directive expands to a prop and an event, and the component’s author decides which prop and which event. The default is modelValue and update:modelValue, but the names can be customized, and the component can support multiple v-model bindings at once.

The underlying mechanism is the same as the fallthrough attributes: the parent passes a prop, and the child emits an event. The v-model directive is the shorthand that wires the two together. When the child emits the update:modelValue event, the parent’s binding is updated with the event’s payload. The child does not mutate the prop — it cannot, because the prop is readonly — and the parent does not reach into the child. The event is the contract, and the prop is the state.

The customization has three parts. The default v-model uses the modelValue prop and the update:modelValue event. The named v-model:title uses the title prop and the update:title event. The argument in the directive is the name, and the component declares the prop and emits the event with the update: prefix. A component can have multiple named bindings, each with its own prop and event, and the parent uses them independently.

This chapter covers three areas. First, why component v-model exists — the prop-and-event pattern and the syntactic sugar. Second, how to implement the default and the named v-model — the defineProps declaration, the defineEmits declaration, and the update: event naming. Third, the advanced patterns — the multiple bindings, the modifiers, the writable computed, and the custom v-model argument. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the binding flow.

Key point: v-model on a component is the shorthand for a prop and an update: event. The default uses the modelValue prop and the update:modelValue event. The named v-model:title uses the title prop and the update:title event. The component declares the prop with defineProps and emits the event with defineEmits. The prop is readonly, and the event is the only way to change the parent’s state.


Why component v-model exists

The prop-and-event problem. The communication between a parent and a child is one-way: the prop flows down, and the event flows up. A two-way binding is a prop and an event that are paired. The parent passes the value as a prop, and the child emits the new value as an event. The parent listens to the event and updates the value. The pattern is repeated for every two-way binding, and the boilerplate is the same: the prop, the listener, and the update.

The syntactic-sugar problem. The v-model directive is the shorthand for the pattern. On a native input, the compiler expands the directive into the prop and the event. On a component, the compiler does the same, and the component’s author chooses the names. The expansion is the contract: v-model="value" becomes :modelValue="value" and @update:modelValue="value = $event". The component declares the modelValue prop and emits the update:modelValue event.

The naming problem. The default names are modelValue and update:modelValue. The names are conventional, and they are what the compiler expects when the directive has no argument. The named binding v-model:title changes the names to title and update:title. The component declares the title prop and emits the update:title event. The names are the contract, and the component’s author chooses them.

The multiple-binding problem. A component that needs more than one two-way binding uses the named v-model for each. A UserForm component with the firstName and the lastName uses v-model:firstName and v-model:lastName. Each binding has its own prop and event, and the parent uses them independently. The alternative — a single object prop with the multiple fields — is less ergonomic and less type-safe.

The modifier problem. A v-model can have a modifier: v-model.trim, v-model.number, v-model.lazy. The modifier is passed to the component, and the component decides how to handle it. The component reads the modelModifiers prop (or the argModifiers for the named bindings) and applies the modifier’s behavior. The pattern is the way the component library exposes the modifiers like the native inputs.

The trade-off. The v-model is convenient, but it hides the prop-and-event pair. A developer who does not know the expansion can be confused about where the value comes from and how the update happens. The named bindings add the verbosity to the template, and the multiple bindings can make the component’s interface harder to read. The trade-off is between the convenience of the shorthand and the explicitness of the prop-and-event pair.


a. The default v-model

The default v-model on a component uses the modelValue prop and the update:modelValue event. The component declares the prop and emits the event.

<!-- CustomInput.vue -->
<script setup lang="ts">
defineProps<{
  modelValue: string;
}>();

const emit = defineEmits<{
  'update:modelValue': [value: string];
}>();

function handleInput(event: Event) {
  const target = event.target as HTMLInputElement;
  emit('update:modelValue', target.value);
}
</script>

<template>
  <input :value="modelValue" @input="handleInput" />
</template>

The parent uses the component with the v-model:

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

const name = ref('');
</script>

<template>
  <CustomInput v-model="name" />
  <p>Name: {{ name }}</p>
</template>

The v-model="name" expands to :modelValue="name" and @update:modelValue="name = $event". The modelValue prop passes the value down, and the update:modelValue event passes the new value up. The name ref is updated when the input changes.

The declaration. The defineProps declares the modelValue prop, and the defineEmits declares the update:modelValue event. The event’s payload is the new value. The handleInput function reads the input’s value and emits the event.

The prop is readonly. The component cannot assign to the modelValue prop. The only way to change the value is to emit the update:modelValue event, and the parent updates its own state.

The template binding. The :value="modelValue" binds the prop to the input’s value, and the @input="handleInput" listens to the input’s change. The handleInput emits the update:modelValue event with the new value.


b. The named v-model

The named v-model:title uses the title prop and the update:title event. The component declares the prop and emits the event with the update: prefix.

<!-- CustomInput.vue with the named binding -->
<script setup lang="ts">
defineProps<{
  title: string;
  content: string;
}>();

const emit = defineEmits<{
  'update:title': [value: string];
  'update:content': [value: string];
}>();
</script>

<template>
  <input
    :value="title"
    @input="emit('update:title', ($event.target as HTMLInputElement).value)"
  />
  <textarea
    :value="content"
    @input="emit('update:content', ($event.target as HTMLTextAreaElement).value)"
  />
</template>

The parent uses the component with the two named bindings:

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

const title = ref('');
const content = ref('');
</script>

<template>
  <CustomInput v-model:title="title" v-model:content="content" />
  <p>Title: {{ title }}</p>
  <p>Content: {{ content }}</p>
</template>

The v-model:title="title" expands to :title="title" and @update:title="title = $event". The v-model:content="content" expands to :content="content" and @update:content="content = $event". The two bindings are independent, and each has its own prop and event.

The naming rule. The argument in the v-model:name is the prop name, and the event name is update:name. The component declares the prop with the argument name and emits the event with the update: prefix.

The multiple bindings. A component can have any number of the named bindings. Each one is a prop-and-event pair, and the parent uses them independently. The component’s interface is explicit: the props are the bindings, and the events are the updates.

The combination with the default. A component can have the default v-model and the named bindings at the same time. The default uses the modelValue and the update:modelValue, and the named bindings use their own names.


c. The advanced patterns

The writable computed. A component that needs to transform the value before emitting uses a writable computed. The computed reads from the prop and writes to the emit.

<script setup lang="ts">
import { computed } from 'vue';

const props = defineProps<{
  modelValue: string;
}>();

const emit = defineEmits<{
  'update:modelValue': [value: string];
}>();

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

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

The value computed reads from the modelValue prop and emits the update:modelValue event when it is set. The v-model="value" on the input binds the computed, and the input’s change flows through the computed to the parent. The pattern is useful when the component needs to transform the value, such as the trimming or the parsing.

The modifiers. A v-model can have the modifiers, and the component receives them in the modelModifiers prop (or the argModifiers for the named bindings).

<!-- CustomInput.vue with the modifiers -->
<script setup lang="ts">
const props = defineProps<{
  modelValue: string;
  modelModifiers?: {
    capitalize?: boolean;
  };
}>();

const emit = defineEmits<{
  'update:modelValue': [value: string];
}>();

function handleInput(event: Event) {
  const target = event.target as HTMLInputElement;
  let value = target.value;
  if (props.modelModifiers?.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1);
  }
  emit('update:modelValue', value);
}
</script>

<template>
  <input :value="modelValue" @input="handleInput" />
</template>

The parent uses the modifier:

<CustomInput v-model.capitalize="name" />

The modelModifiers prop contains the modifiers, and the component applies them. The named binding uses the argModifiers prop: v-model:title.capitalize populates the titleModifiers prop.

The defineModel macro. Vue 3.4 introduced the defineModel macro, which simplifies the v-model implementation. The macro declares a ref that is automatically wired to the prop and the event.

<script setup lang="ts">
const model = defineModel<string>();

function handleInput(event: Event) {
  model.value = (event.target as HTMLInputElement).value;
}
</script>

<template>
  <input :value="model" @input="handleInput" />
</template>

The defineModel<string>() returns a ref that reads from the modelValue prop and emits the update:modelValue event when it is assigned. The defineModel macro removes the boilerplate of the defineProps and the defineEmits for the v-model. The named binding is declared with the argument: const title = defineModel<string>('title') .

The defineModel with the default and the required. The defineModel macro accepts the options: the default and the required. The macro is the modern way to implement the v-model, and the manual defineProps and defineEmits are the fallback for the cases the macro does not cover.

const model = defineModel<string>({ default: '' });
const model = defineModel<string>({ required: true });

Complete Example Session

<!-- ============================================
     PART 1: THE DEFAULT v-model
     ============================================ -->

<!-- CustomInput.vue -->
<script setup lang="ts">
defineProps<{
  modelValue: string;
}>();

const emit = defineEmits<{
  'update:modelValue': [value: string];
}>();

function handleInput(event: Event) {
  const target = event.target as HTMLInputElement;
  emit('update:modelValue', target.value);
}
</script>

<template>
  <input :value="modelValue" @input="handleInput" />
</template>


<!-- ============================================
     PART 2: THE PARENT USING THE DEFAULT v-model
     ============================================ -->

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

const name = ref('');
</script>

<template>
  <CustomInput v-model="name" />
  <p>Name: {{ name }}</p>
</template>

<!-- v-model="name" expands to:
     :modelValue="name"
     @update:modelValue="name = $event" -->


<!-- ============================================
     PART 3: THE NAMED v-model
     ============================================ -->

<!-- UserForm.vue -->
<script setup lang="ts">
defineProps<{
  firstName: string;
  lastName: string;
}>();

const emit = defineEmits<{
  'update:firstName': [value: string];
  'update:lastName': [value: string];
}>();
</script>

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


<!-- ============================================
     PART 4: THE PARENT USING THE NAMED v-model
     ============================================ -->

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

const first = ref('');
const last = ref('');
</script>

<template>
  <UserForm v-model:firstName="first" v-model:lastName="last" />
  <p>{{ first }} {{ last }}</p>
</template>


<!-- ============================================
     PART 5: THE WRITABLE COMPUTED
     ============================================ -->

<script setup lang="ts">
import { computed } from 'vue';

const props = defineProps<{
  modelValue: string;
}>();

const emit = defineEmits<{
  'update:modelValue': [value: string];
}>();

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

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

<!-- The value is trimmed before it is emitted. -->


<!-- ============================================
     PART 6: THE MODIFIERS
     ============================================ -->

<script setup lang="ts">
const props = defineProps<{
  modelValue: string;
  modelModifiers?: {
    capitalize?: boolean;
  };
}>();

const emit = defineEmits<{
  'update:modelValue': [value: string];
}>();

function handleInput(event: Event) {
  const target = event.target as HTMLInputElement;
  let value = target.value;
  if (props.modelModifiers?.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1);
  }
  emit('update:modelValue', value);
}
</script>

<template>
  <input :value="modelValue" @input="handleInput" />
</template>

<!-- The parent: <CustomInput v-model.capitalize="name" /> -->


<!-- ============================================
     PART 7: THE defineModel MACRO
     ============================================ -->

<script setup lang="ts">
const model = defineModel<string>();

function handleInput(event: Event) {
  model.value = (event.target as HTMLInputElement).value;
}
</script>

<template>
  <input :value="model" @input="handleInput" />
</template>

<!-- The defineModel macro removes the boilerplate. -->


<!-- ============================================
     PART 8: THE defineModel WITH THE OPTIONS
     ============================================ -->

<script setup lang="ts">
const model = defineModel<string>({ default: '' });
const title = defineModel<string>('title', { required: true });
</script>


<!-- ============================================
     PART 9: THE MULTIPLE defineModel
     ============================================ -->

<script setup lang="ts">
const firstName = defineModel<string>('firstName');
const lastName = defineModel<string>('lastName');
</script>

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

<!-- The parent:
     <UserForm v-model:firstName="first" v-model:lastName="last" /> -->


<!-- ============================================
     PART 10: THE COMPLETE COMPONENT
     ============================================ -->

<!-- SearchBox.vue -->
<script setup lang="ts">
import { computed } from 'vue';

const model = defineModel<string>({ default: '' });
const props = defineProps<{
  placeholder?: string;
  modelModifiers?: { trim?: boolean };
}>();

const emit = defineEmits<{
  search: [query: string];
}>();

function handleInput(event: Event) {
  const target = event.target as HTMLInputElement;
  let value = target.value;
  if (props.modelModifiers?.trim) {
    value = value.trim();
  }
  model.value = value;
}

function handleSearch() {
  emit('search', model.value);
}
</script>

<template>
  <div class="search-box">
    <input
      :value="model"
      :placeholder="placeholder"
      @input="handleInput"
      @keyup.enter="handleSearch"
    />
    <button @click="handleSearch">Search</button>
  </div>
</template>

<!-- The parent:
     <SearchBox v-model.trim="query" @search="onSearch" /> -->

The ten parts show the default v-model, the parent using it, the named v-model, the parent using the named bindings, the writable computed, the modifiers, the defineModel macro, the defineModel with the options, the multiple defineModel, and the complete component.


Quick Reference

The Default v-model

TemplateExpands to
v-model="value":modelValue="value" + @update:modelValue="value = $event"
The propmodelValue
The eventupdate:modelValue
The modifier propmodelModifiers

The Named v-model

TemplateExpands to
v-model:title="title":title="title" + @update:title="title = $event"
The proptitle
The eventupdate:title
The modifier proptitleModifiers

The Implementation

ApproachDeclaration
ManualdefineProps + defineEmits + emit
defineModelconst model = defineModel<T>()
The named defineModelconst title = defineModel<T>('title')
The optionsdefineModel<T>({ default, required })

The Modifiers

ModifierPropBehavior
.trimmodelModifiers.trimTrim the value
.numbermodelModifiers.numberParse the number
.capitalizemodelModifiers.capitalizeCustom
The namedargModifiersThe modifier for the named binding

The Writable Computed

PartCode
The getget: () => props.modelValue
The setset: (v) => emit('update:modelValue', v)
The templatev-model="value"

Best Practices

✅ Do This:

<script setup>
// Use defineModel for the simple cases
const model = defineModel<string>();                                      // ✅
// Use the named defineModel for the multiple bindings
const firstName = defineModel<string>('firstName');                       // ✅
// Use the manual defineProps/defineEmits for the complex cases
defineProps<{ modelValue: string }>();
defineEmits<{ 'update:modelValue': [value: string] }>();                  // ✅
// Use the writable computed for the transformations
const value = computed({ get, set });                                     // ✅
// Declare the modifier prop
defineProps<{ modelModifiers?: { trim?: boolean } }>();                   // ✅
</script>

❌ Don’t Do This:

<script setup>
// Don't mutate the modelValue prop directly
modelValue.value = 'new'; // the prop is readonly                          // ❌
// Don't forget to emit the update event
function handleInput(e) { /* no emit */ }                                 // ❌
// Don't use the wrong event name
emit('update:value', x); // should be 'update:modelValue'                 // ❌
// Don't forget the named modifier prop
// v-model:title.trim needs the titleModifiers prop                       // ❌
// Don't mix the manual and the defineModel for the same binding
const model = defineModel(); defineProps<{ modelValue: string }>();       // ❌
</script>

Common Pitfalls

PitfallWhy It HappensFix
The v-model does not workThe wrong prop or event nameUse modelValue and update:modelValue
The prop is readonlyMutating the modelValueEmit the event
The named binding does not workThe wrong argumentUse the v-model:name and the update:name
The modifier is ignoredNo modelModifiers propDeclare the prop
The defineModel is not availableVue < 3.4Use the manual defineProps/defineEmits
The value is not updatedNo emit callEmit the update:modelValue
The multiple bindings conflictThe same prop nameUse the distinct names

Real-World Examples

1. The Default v-model

<CustomInput v-model="name" />

2. The Named v-model

<UserForm v-model:firstName="first" v-model:lastName="last" />

3. The defineModel

const model = defineModel<string>();

4. The Named defineModel

const title = defineModel<string>('title');

5. The Manual Implementation

defineProps<{ modelValue: string }>();
defineEmits<{ 'update:modelValue': [value: string] }>();

6. The Writable Computed

const value = computed({ get: () => props.modelValue, set: (v) => emit('update:modelValue', v) });

7. The Modifier

defineProps<{ modelModifiers?: { trim?: boolean } }>();

8. The defineModel with the Options

const model = defineModel<string>({ default: '' });

9. The Multiple defineModel

const firstName = defineModel<string>('firstName');

10. The Complete Search Box

<SearchBox v-model.trim="query" @search="onSearch" />

Visual

The v-model Expansion

┌──────────────────────────────────────────────────────────────┐
│  THE v-model EXPANSION                                       │
│                                                              │
│  Parent:                                                     │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <CustomInput v-model="name" />                      │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  The compiler expands the directive.                    │
│    ▼                                                         │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <CustomInput                                        │    │
│  │    :modelValue="name"                                │    │
│  │    @update:modelValue="name = $event"                │    │
│  │  />                                                  │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  The prop flows down.                                   │
│    │  The event flows up.                                    │
│    ▼                                                         │
│  Child:                                                      │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  defineProps<{ modelValue: string }>()               │    │
│  │  defineEmits<{ 'update:modelValue': [value: string] }>()
│  │                                                       │    │
│  │  <input :value="modelValue" @input="handleInput" />  │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Named v-model

┌──────────────────────────────────────────────────────────────┐
│  THE NAMED v-model                                           │
│                                                              │
│  Parent:                                                     │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <UserForm                                           │    │
│  │    v-model:firstName="first"                         │    │
│  │    v-model:lastName="last"                           │    │
│  │  />                                                  │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  Expands to:                                            │
│    ▼                                                         │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <UserForm                                           │    │
│  │    :firstName="first"                                │    │
│  │    @update:firstName="first = $event"                │    │
│  │    :lastName="last"                                  │    │
│  │    @update:lastName="last = $event"                  │    │
│  │  />                                                  │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  Child:                                                      │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  defineProps<{                                       │    │
│  │    firstName: string;                                │    │
│  │    lastName: string;                                 │    │
│  │  }>()                                                │    │
│  │  defineEmits<{                                       │    │
│  │    'update:firstName': [value: string];              │    │
│  │    'update:lastName': [value: string];               │    │
│  │  }>()                                                │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The defineModel Macro

┌──────────────────────────────────────────────────────────────┐
│  THE defineModel MACRO                                       │
│                                                              │
│  WITHOUT defineModel:                                        │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  const props = defineProps<{ modelValue: string }>();│    │
│  │  const emit = defineEmits<{                          │    │
│  │    'update:modelValue': [value: string];             │    │
│  │  }>();                                               │    │
│  │                                                       │    │
│  │  function handleInput(e) {                           │    │
│  │    emit('update:modelValue', e.target.value);        │    │
│  │  }                                                   │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  WITH defineModel:                                           │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  const model = defineModel<string>();                │    │
│  │                                                       │    │
│  │  function handleInput(e) {                           │    │
│  │    model.value = e.target.value;                     │    │
│  │  }                                                   │    │
│  │                                                       │    │
│  │  The macro wires the prop and the event.             │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Writable Computed

┌──────────────────────────────────────────────────────────────┐
│  THE WRITABLE COMPUTED                                       │
│                                                              │
│  const props = defineProps<{ modelValue: string }>();        │
│  const emit = defineEmits<{                                  │
│    'update:modelValue': [value: string];                     │
│  }>();                                                       │
│                                                              │
│  const value = computed({                                    │
│    get: () => props.modelValue,                              │
│    set: (newValue) => emit('update:modelValue', newValue.trim()),
│  });                                                         │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <input v-model="value" />                           │    │
│  │                                                       │    │
│  │  The input's change:                                 │    │
│  │    → value.value = newValue                          │    │
│  │    → the setter runs                                 │    │
│  │    → emit('update:modelValue', trimmed)              │    │
│  │    → the parent updates its state                    │    │
│  │    → the prop updates                                │    │
│  │    → the getter returns the new value                │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  The transformation (trim) happens in the setter.            │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
v-model on a componentThe prop + the update: event
The default propmodelValue
The default eventupdate:modelValue
The named bindingv-model:title → title + update:title
The modifier propmodelModifiers (the default)
The named modifier propargModifiers
The defineModel macroVue 3.4+; the ref-based declaration
The writable computedThe get/set for the transformations
The prop readonlyThe child cannot mutate the prop

Key takeaways:

  • v-model on a component is the shorthand for a prop and an update: event. The default uses the modelValue prop and the update:modelValue event. The compiler expands the directive into the prop binding and the event listener .
  • The component declares the prop with defineProps and emits the event with defineEmits. The prop is readonly, and the event is the only way to change the parent’s state. The event’s payload is the new value .
  • The named v-model:title uses the title prop and the update:title event. The argument in the directive is the prop name, and the event name is the update: prefix plus the argument. The component can have the multiple named bindings .
  • The defineModel macro simplifies the implementation. The macro declares a ref that is automatically wired to the prop and the event. The defineModel<string>() is the modern replacement for the manual defineProps and defineEmits .
  • The modifiers are passed to the component. The v-model.trim populates the modelModifiers prop, and the v-model:title.trim populates the titleModifiers prop. The component reads the modifiers and applies the behavior .
  • The writable computed is the pattern for the transformations. The get returns the prop, and the set emits the event. The transformation (the trim, the parse) happens in the setter .
  • The prop is readonly. The component cannot assign to the modelValue prop directly. The only way to change the value is to emit the update:modelValue event, and the parent updates its own state .
  • The defineModel macro is available in Vue 3.4 and later. The projects on the earlier versions use the manual defineProps and defineEmits. The macro removes the boilerplate and makes the implementation more concise .

Remember: The v-model on a component is the syntactic sugar for the prop-and-event pair. The default uses the modelValue prop and the update:modelValue event, and the named bindings use the v-model:name syntax with the name prop and the update:name event. The component declares the prop with defineProps and emits the event with defineEmits, or it uses the defineModel macro. The modifiers are passed in the modelModifiers or the argModifiers prop, and the writable computed is the pattern for the transformations. The result is a component that supports the two-way binding with the explicit prop-and-event contract, the multiple bindings, and the modifiers.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!