How defineModel simplifies v-model in custom Vue components

Published: (February 25, 2026 at 05:35 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

In this article I demonstrate a practical use case for defineModel: a custom modal component that controls its visibility without needing a modelValue prop, an update:modelValue emit, or any boilerplate sync logic. defineModel replaces the traditional modelValue + update:modelValue pattern with a single reactive binding.

Using the Modal


import { ref } from 'vue';
import MyModal from './components/MyModal.vue';

const isModalOpen = ref(false);

  
    Open Modern Modal

    
      
## Custom Content

      
This replaces the default slot content!

    
  
  • isModalOpen is just a ref.
  • v-model controls the modal’s visibility.
  • The default slot lets the parent pass custom content, while the parent component remains unaware of the modal’s internal implementation.

👉 Live demo (StackBlitz)


import { useTemplateRef, watchEffect, defineModel } from 'vue';

// The magic: two‑way binding in one line
const isVisible = defineModel({ default: false });
const modal = useTemplateRef('modal');

watchEffect(() => {
  if (!modal.value) return;

  isVisible.value ? modal.value.showModal() : modal.value.close();
});

  
    
      
## Default Modal Title

      
This is the default content of your slot.

    
    Close
  

What defineModel Does

const isVisible = defineModel({ default: false });
  • Returns a reactive value.
  • Provides automatic v-model support.
  • Enables two‑way binding without any explicit emit code.

Previously you would need:

const props = defineProps({ modelValue: Boolean });
const emit = defineEmits(['update:modelValue']);
// plus manual sync logic

Now it’s reduced to a single line.

Why a Modal Is a Perfect Fit

  • It has a single source of truth: open / closed.
  • The parent wants to control when it opens.
  • The component itself needs internal control (close button, ESC key, etc.).

With defineModel, both sides can update the same value:

  • Parent opens the modal → isVisible becomes true.
  • Modal closes itself → isVisible becomes false.

The state stays in sync automatically—no custom events, no glue code.

Benefits of Using defineModel

  • Cleaner API – the component behaves like a native input.
  • Predictable behavior – only one source of truth.
  • Minimal code – eliminates the “prop + emit + sync” ceremony.
  • Reduced mental overhead – think of the component as having a single value rather than juggling multiple props and events.

Good Fit When

  • The component has a single main state.
  • Both parent and child need to update that state.
  • You want a clean v-model API.

Example Scenarios

  • Modal / dialog
  • Toggle switch
  • Tabs
  • Drawer
  • Select dropdown
  • Stepper

Conclusion

defineModel is a small API change that dramatically simplifies the way we write custom Vue components. Instead of boilerplate, you get:

  • One line to declare the bound value.
  • One state that is shared automatically.
  • One API (v-model) for consumers.

If you haven’t tried defineModel yet, give it a shot in a real component. It makes v-model usage clearer and custom components easier to reason about.

Feel free to share your own use cases or suggestions in the comments.

0 views
Back to Blog

Related posts

Read more »

My Developer Portfolio — Umer Azmi

Hi, I'm Umer Azmi, a Frontend Developer and Python Developer from Mumbai, India. Projects and Contributions 👉 https://github.com/UmerAzmihttps://github.com/Ume...