Skip to content

Stop using v-model on Vuetify's Dialog Component

vue3

Vuetify provides a versatile modal component called VDialog.

A common pattern is controlling dialog visibility with v-model, but this often introduces unnecessary boilerplate:

  1. You have to declare and track a reactive variable (const isOpen = ref(false)).
  2. You need to create an activator button with explicit click handlers to toggle that state.

It typically looks like this:

<template>
  <v-btn @click="modal = true">Open</v-btn>

  <v-dialog v-model="modal">
    <v-card>
      <v-card-text>Content</v-card-text>
      <v-card-actions>
        <v-btn @click="modal = false">Close</v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

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

const modal = ref(false);
</script>

For a single dialog, this might not seem like a big deal. However, as a component grows to handle multiple dialogs—such as creating, editing, and deleting records—you quickly find yourself juggling multiple ref variables, button handlers, and state management logic.

A Cleaner Alternative: Scoped Slots

Instead of managing state manually, you can leverage the scoped slots provided natively by v-dialog:

<template>
  <v-dialog>
    <!-- Activator slot provides accessibility and click bindings -->
    <template #activator="{ props: activatorProps }">
      <v-btn v-bind="activatorProps">Open</v-btn>
    </template>

    <!-- Default slot provides the isActive ref to toggle state from inside -->
    <template #default="{ isActive }">
      <v-card>
        <v-card-title>Dialog Title</v-card-title>
        <v-card-text>Content goes here.</v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn @click="isActive.value = false">Close</v-btn>
        </v-card-actions>
      </v-card>
    </template>
  </v-dialog>
</template>

Why this is better:

  • Zero script boilerplate: No <script> block or reactive ref variables needed for self-contained modals.
  • Built-in accessibility: v-bind="activatorProps" automatically handles ARIA attributes and click bindings.
  • Encapsulated state: Each dialog manages its own open/close lifecycle independently.

(Note: v-model is still useful when you need to trigger a dialog programmatically from outside the template, such as in response to an API error or a router navigation guard).

End of article