Dialog
A window over the page that holds the user until they close it.
<template> <Dialog v-model:open="open" title="Delete repository" description="This can't be undone."> <template #footer> <Button variant="destructive" @click="onConfirm">Delete</Button> </template> </Dialog></template>Props
| Name | Type | Default |
|---|---|---|
| open | boolean (v-model:open) | false |
| title | string | - |
| description | string | - |
| close | boolean | object | true |
| fullscreen | boolean | false |
Slots
| Name | Description |
|---|---|
| body | The dialog's main content. |
| footer | Action buttons, right-aligned by default (justify-end) - matches apps/frontend's own confirm-modal.vue: cancel as color="neutral" variant="ghost", the primary action last in its default solid color (or color="error" for a destructive confirm). |
| title | Escape hatch: replaces the `title` prop's plain text. |
| description | Escape hatch: replaces the `description` prop's plain text. |
Guidelines
Do
- Use
title/descriptionprops (or the#title/#descriptionslots when they need more than plain text) instead of hand-building a header inside#body- that's the pattern every real modal inapps/frontendhad to repeat by hand because there was no shared wrapper to fix the background color once. - Put action buttons directly in the
#footerslot - it's already right-aligned with an 8px gap by default, no wrapping div needed. Put the least destructive action first: cancel/dismiss asvariant="ghost", the primary action last in its default solid color. This matches the realconfirm-modal.vuepattern already used by 27 call sites inapps/frontend. - For a destructive confirmation specifically, use
variant="destructive"on the confirm button - the same defaultapps/frontend's shared confirm modal already uses.
- Bind
v-model:opento a localref(false)- every real usage does this; none rely on a default-slot trigger or purely imperative open/close.
Don't
- Don't hand-build a close (×) button in the header -
closeistrueby default (matching what every real modal already renders, just via a manually-built button instead of the native one) and already positioned/styled correctly; only passclosewhen you actually want to hide it. - Don't pass a fixed pixel width (
:ui="{ content: 'w-[500px]' }") unless the content genuinely needs it - most real modals just take the stockmax-w-lg, and the few that don't are the exception, not the rule. - Don't reach for
Dialogfor something the user should be able to dismiss and forget about (a save confirmation, an undo prompt) - use a toast (useAppToast()) instead; a modal demands the user's attention before they can do anything else.
- Don't expect
bg-surface-popover- despite that being what most real modals inapps/frontendhappened to use,Dialogusesbg-surface-backgroundinstead, plus a plain 16px gap between header/body/footer (no divider line) and explicit title/description type sizes that don't match Nuxt UI's own stock modal styles at all.
Examples
Basic
A modal for something the user can safely dismiss and return to later.
const open = ref(false)<Button variant="secondary" @click="open = true">Edit profile</Button><Dialog v-model:open="open" title="Edit profile" description="Make changes to your profile here. Click save when you're done."> <template #body> <Input placeholder="Display name" class="w-full" /> </template> <template #footer> <Button variant="primary" @click="open = false">Save changes</Button> </template></Dialog>Destructive confirm
A dialog housing just the one destructive action, with no separate cancel button.
const open = ref(false)<Button variant="secondary" @click="open = true">Delete repository</Button><Dialog v-model:open="open" title="Delete repository" description="This will permanently delete the repository and all of its scan history. This can't be undone."> <template #footer> <Button variant="destructive" @click="open = false">Delete</Button> </template></Dialog>