Building Accessible Modals with the HTML Dialog Element in Vue.js
Discover how the native HTML dialog element revolutionizes modal creation in Vue.js applications, providing built-in accessibility features and simplified implementation patterns.

Discover how the native HTML dialog element revolutionizes modal creation in Vue.js applications, providing built-in accessibility features and simplified implementation patterns.
The HTML <dialog>
element is a game-changer for creating modals in Vue.js applications. It provides built-in accessibility features and eliminates common pain points of custom modal implementations.
Traditional modals often suffer from:
The <dialog>
element solves these with:
Here's a simple modal component using the dialog element:
<template>
<dialog ref="dialogRef" class="modal" @click="handleBackdropClick">
<div class="modal-content">
<header>
<h2>{{ title }}</h2>
</header>
<div class="modal-body">
<slot />
</div>
<footer>
<button @click="close">Cancel</button>
<button @click="confirm">Confirm</button>
</footer>
</div>
</dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const props = defineProps<{
open: boolean;
title: string;
}>();
const emit = defineEmits<{
close: [];
confirm: [];
}>();
const dialogRef = ref<HTMLDialogElement>();
const showModal = () => dialogRef.value?.showModal();
const closeModal = () => dialogRef.value?.close();
const close = () => {
closeModal();
emit('close');
};
const confirm = () => {
emit('confirm');
close();
};
const handleBackdropClick = (event: MouseEvent) => {
if (event.target === dialogRef.value) {
close();
}
};
watch(
() => props.open,
(newValue) => {
if (newValue) showModal();
else closeModal();
},
{ immediate: true },
);
</script>
<style scoped>
.modal {
border: none;
border-radius: 8px;
padding: 0;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.modal::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(2px);
}
.modal-content {
padding: 1.5rem;
min-width: 400px;
}
</style>
<template>
<button @click="showModal = true">Delete Item</button>
<ConfirmModal
:open="showModal"
title="Confirm Deletion"
@close="showModal = false"
@confirm="handleDelete"
>
Are you sure you want to delete this item?
</ConfirmModal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ConfirmModal from './ConfirmModal.vue';
const showModal = ref(false);
const handleDelete = () => {
// Delete logic here
console.log('Item deleted!');
};
</script>
The dialog element works seamlessly with forms:
<template>
<dialog ref="dialogRef">
<form method="dialog" @submit="handleSubmit">
<input type="text" v-model="username" required />
<button type="submit">Save</button>
<button type="button" value="cancel">Cancel</button>
</form>
</dialog>
</template>
Enhance focus behavior:
<script setup lang="ts">
import { nextTick } from 'vue';
const focusFirstElement = async () => {
await nextTick();
const firstFocusable = dialogRef.value?.querySelector(
'button, input, select, textarea, [tabindex]:not([tabindex="-1"])',
) as HTMLElement;
firstFocusable?.focus();
};
watch(
() => props.open,
(newValue) => {
if (newValue) {
showModal();
focusFirstElement();
}
},
);
</script>
For older browsers, use the dialog-polyfill library.
The HTML <dialog>
element provides a powerful, accessible foundation for modal interfaces in Vue.js applications. It reduces complexity, improves accessibility by default, and creates more consistent user experiences.
By leveraging this native web platform feature, you can focus on business logic rather than infrastructure code.
AI has been used to correct sentence flow and improve readability.