How to use Vue's new DefineModel macro

I'm building out the registration/login flow for my indoor rowing workout tracking application. I need a way that users can toggle the password input from 'password' to 'text' so they can see their password when typing.

Why does DefineModel exist?

Before defineModel, creating two-way binding in Vue required developers to declare props, emits and an event handler to handle child to parent state changes. This setup becomes difficult to maintain and scale as the application grows and changes.

defineModel() is a macro that provides a much cleaner and simpler implementation.

This is a very simple example of how Child component can update a count value passed in from the Parent component. This code below is used in the demo below:

<!-- Parent.vue -->
<script setup lang="ts">
const count = ref(0);
</script>

<template>
  <div class="col-span-full my-4 w-full max-w-[600px] mx-auto">
    <p class="m-0">Parent's count value: <span class="badge badge-lg badge-primary">{{ count  }}</span></p>
  </div>
  <div class="col-span-full mt-2 mb-4 w-full max-w-[600px] mx-auto">
    Child Component below has `v-model:count="count"` and is updating the parent's state:
    <br />
    <br />
    <Counter v-model:count="count" />
  </div>
</template>
<!-- Counter.vue -->
<script setup lang="ts">
// Const count is assigned via defineModel() and takes in the 'count' as that's the v-model passed in from the parent.
// We can also assign a type and default to `count`
const count = defineModel('count', { type: Number, default: 0 })

// The update() method can update the `count` value and the parent will automatically see the updated value
const increment = () => count.value++;
</script>

<template>
  <p>Child vue showing 'count' from parent: <span class="badge badge-lg badge-secondary">{{ count  }}</span></p>
  <button @click="increment" class="btn btn-sm btn-secondary">Increment Counter in Child component</button>
</template>

Demo

Parent's count value: 0

Child Component below has `v-model:count="count"` and is updating the parent's state:

Child vue showing 'count' from parent: 0

How to toggle input types

I'm using this to create the following re-usable password type toggle that's displayed in my login and registration forms:

https://www.nonamedomain.com

I've created the following inline demo below with the code sample under it too:

Demo

<!-- PasswordIndicator.vue -->
<script setup lang="ts">
const passwordVisibility = defineModel<Boolean>('passwordVisibility');
const togglePasswordVisibility = () => passwordVisibility.value = !passwordVisibility.value;
</script>

<template>
  <small @click="togglePasswordVisibility" class="cursor-pointer">
    <span>{{ passwordVisibility ? 'πŸ‘πŸ½' : 'πŸ‘ŽπŸ½'}}</span>
  </small>
</template>
<!-- PasswordInput.vue -->
<script setup lang="ts">
const password = ref<string>('');
const canSeePassword = ref<boolean>(false);
</script>

<template>
<div class="col-span-full my-4 w-full max-w-[600px] mx-auto">
  <label for="password"
    class="input input-bordered flex items-center gap-2">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"
      fill="currentColor" class="w-4 h-4 opacity-70">
      <path fill-rule="evenodd"
        d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
        clip-rule="evenodd" />
    </svg>

    <input :type="canSeePassword ? 'text' : 'password'" aria-label="Password for new account"
      id="password" placeholder="Password" v-model="password"
      autocomplete="new-password" class="grow" required />

    <PasswordIndicator v-model:passwordVisibility="canSeePassword" />
  </label>
</div>
</template>

The above PasswordIndicator component can be reused multiple times in a single form if the canSeePassword ref is changed for each input you'd like to control, e.g: a "new password" and "confirm new password" inputs for a Registration form πŸ˜‰.

Wrap up

Hopefully this quick overview of the new defineModel macro helps you create cleaner and reusable Vue components. You can also use v-model's .modifiers with the macro, which will help convert your older code to the newer syntax whilst keeping the same functionality πŸ€“.