Vuetify provides a versatile modal component called VDialog.
A common pattern is controlling dialog visibility with v-model, but this often introduces unnecessary boilerplate:
- You have to declare and track a reactive variable (
const isOpen = ref(false)). - 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 reactiverefvariables 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).