Dialog
A confirmation/alert dialog for user decisions. Use the template component VxDialog, the programmatic useConfirm() composable, or combine with VxModal for multi-step wizards.
Stepped Dialog
A multi-step wizard using VxModal with Previous / Next navigation and a final Save button.
Full Example Code
vue
<script setup>
import { ref, computed } from 'vue'
const show = ref(false)
const step = ref(0)
const saving = ref(false)
const form = ref({
name: '',
email: '',
notifications: true,
theme: 'system',
agree: false,
})
const steps = [
{ title: 'Account', desc: 'Basic information' },
{ title: 'Preferences', desc: 'Customize experience' },
{ title: 'Confirm', desc: 'Review & save' },
]
const isFirst = computed(() => step.value === 0)
const isLast = computed(() => step.value === steps.length - 1)
function open() {
step.value = 0
form.value = { name: '', email: '', notifications: true, theme: 'system', agree: false }
saving.value = false
show.value = true
}
function next() { if (!isLast.value) step.value++ }
function prev() { if (!isFirst.value) step.value-- }
function save() {
saving.value = true
setTimeout(() => {
saving.value = false
show.value = false
// submit form.value to API...
}, 1500)
}
</script>
<template>
<button @click="open">Open Wizard</button>
<VxModal v-model="show" size="sm" persistent :closable="false">
<!-- Header with step indicator -->
<template #header>
<div style="width: 100%">
<div style="display: flex; justify-content: space-between; margin-bottom: 16px">
<h3>Create Account</h3>
<span>Step {{ step + 1 }} of {{ steps.length }}</span>
</div>
<div class="wiz-steps">
<div
v-for="(s, i) in steps" :key="i"
class="wiz-step"
:class="{ 'wiz-step--active': i === step, 'wiz-step--done': i < step }"
>
<div class="wiz-dot">
<svg v-if="i < step" width="12" height="12" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12l5 5L20 7" />
</svg>
<span v-else>{{ i + 1 }}</span>
</div>
<div class="wiz-label">
<div class="wiz-label-title">{{ s.title }}</div>
<div class="wiz-label-desc">{{ s.desc }}</div>
</div>
</div>
</div>
</div>
</template>
<!-- Step content with slide transition -->
<Transition name="wiz-fade" mode="out-in">
<!-- Step 1: Account -->
<div v-if="step === 0" key="s0" class="wiz-body">
<label class="wiz-field">
<span class="wiz-field-label">Full Name</span>
<input v-model="form.name" placeholder="John Doe" class="wiz-input" />
</label>
<label class="wiz-field">
<span class="wiz-field-label">Email</span>
<input v-model="form.email" type="email" placeholder="john@example.com"
class="wiz-input" />
</label>
</div>
<!-- Step 2: Preferences -->
<div v-else-if="step === 1" key="s1" class="wiz-body">
<label class="wiz-field wiz-field--row">
<span class="wiz-field-label">Email notifications</span>
<VxSwitch v-model="form.notifications" />
</label>
<div class="wiz-field">
<span class="wiz-field-label">Theme</span>
<div class="wiz-radios">
<label class="wiz-radio"
:class="{ 'wiz-radio--active': form.theme === 'light' }">
<input v-model="form.theme" type="radio" value="light" /> Light
</label>
<label class="wiz-radio"
:class="{ 'wiz-radio--active': form.theme === 'dark' }">
<input v-model="form.theme" type="radio" value="dark" /> Dark
</label>
<label class="wiz-radio"
:class="{ 'wiz-radio--active': form.theme === 'system' }">
<input v-model="form.theme" type="radio" value="system" /> System
</label>
</div>
</div>
</div>
<!-- Step 3: Review & Confirm -->
<div v-else key="s2" class="wiz-body">
<div class="wiz-review">
<div class="wiz-review-row">
<span class="wiz-review-label">Name</span>
<span class="wiz-review-value">{{ form.name || '—' }}</span>
</div>
<div class="wiz-review-row">
<span class="wiz-review-label">Email</span>
<span class="wiz-review-value">{{ form.email || '—' }}</span>
</div>
<div class="wiz-review-row">
<span class="wiz-review-label">Notifications</span>
<span class="wiz-review-value">{{ form.notifications ? 'On' : 'Off' }}</span>
</div>
<div class="wiz-review-row">
<span class="wiz-review-label">Theme</span>
<span class="wiz-review-value">{{ form.theme }}</span>
</div>
</div>
<label class="wiz-field wiz-field--row" style="margin-top: 12px">
<input v-model="form.agree" type="checkbox" />
<span>I agree to the Terms and Privacy Policy</span>
</label>
</div>
</Transition>
<!-- Footer: Previous / Next / Save -->
<template #footer>
<button v-if="isFirst" class="wiz-btn wiz-btn--ghost" @click="show = false">
Cancel
</button>
<button v-else class="wiz-btn wiz-btn--ghost" @click="prev">
← Previous
</button>
<button v-if="!isLast" class="wiz-btn wiz-btn--primary" @click="next">
Next →
</button>
<button v-else class="wiz-btn wiz-btn--save"
:disabled="!form.agree || saving" @click="save">
{{ saving ? 'Saving...' : 'Save Account' }}
</button>
</template>
</VxModal>
</template>Programmatic API (useConfirm)
Call from <script setup> — no template needed. Returns a Promise that resolves on confirm and rejects on cancel.
vue
<script setup>
import { useConfirm } from 'veloxui'
const confirm = useConfirm()
// Basic — string shorthand
function deleteItem() {
confirm('Are you sure?')
.then(() => {
// confirmed
console.log('Deleted!')
})
.catch(() => {
// cancelled
console.log('Cancelled')
})
}
// Typed shorthand — .danger(), .success(), .warning(), .info()
function deleteAccount() {
confirm.danger({
title: 'Delete Account',
description: 'This cannot be undone.',
confirmText: 'Delete',
}).then(() => {
api.deleteAccount()
})
}
// Async / await
async function removeMember() {
try {
await confirm.danger({
title: 'Remove Member',
description: 'They will lose access.',
confirmText: 'Remove',
})
await api.removeMember(id)
} catch {
// user cancelled
}
}
// Info alert (no cancel button)
function showAlert() {
confirm.info({
title: 'Update Available',
confirmText: 'OK',
showCancel: false,
}).then(() => { ... })
}
</script>Template Component
For cases where you need slots or reactive loading state.
Types
vue
<template>
<VxDialog type="info" title="Info" description="Saved." />
<VxDialog type="success" title="Success" confirm-text="Great!" :show-cancel="false" />
<VxDialog type="warning" title="Unsaved Changes" confirm-text="Leave" cancel-text="Stay" />
<VxDialog type="danger" title="Delete" confirm-text="Delete" />
</template>Without Icon
vue
<template>
<VxDialog :icon="false" title="Confirm" />
</template>Confirm Only
vue
<template>
<VxDialog type="success" confirm-text="OK" :show-cancel="false" />
</template>Closable
vue
<template>
<VxDialog closable title="Notice" />
</template>Loading
vue
<template>
<VxDialog type="danger" :loading="isDeleting" @confirm="handleDelete" />
</template>Custom Button Text
vue
<template>
<VxDialog confirm-text="Discard" cancel-text="Keep" />
</template>Custom Content
vue
<template>
<VxDialog v-model="show" title="Rate Limit">
<p>Custom HTML in the default slot.</p>
</VxDialog>
</template>Persistent
vue
<template>
<VxDialog :persistent="false" close-on-overlay />
</template>Sizes
vue
<template>
<VxDialog size="sm" />
<VxDialog size="md" />
<VxDialog size="lg" />
</template>Position
vue
<template>
<VxDialog position="top-left" />
<VxDialog position="top-center" />
<VxDialog position="top-right" />
<VxDialog position="center" /> <!-- default -->
<VxDialog position="bottom-left" />
<VxDialog position="bottom-center" />
<VxDialog position="bottom-right" />
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | false | Show/hide (v-model) |
title | string | '' | Dialog title |
description | string | '' | Description text |
type | 'info' | 'success' | 'warning' | 'danger' | 'info' | Icon and button color |
size | 'sm' | 'md' | 'lg' | 'sm' | Dialog width |
icon | boolean | true | Show type icon |
closable | boolean | false | Show close button |
closeOnOverlay | boolean | false | Close on overlay click |
closeOnEsc | boolean | true | Close on Escape |
confirmText | string | 'Confirm' | Confirm button text |
cancelText | string | 'Cancel' | Cancel button text |
showCancel | boolean | true | Show cancel button |
loading | boolean | false | Loading state |
persistent | boolean | true | Shake on overlay click |
position | 'center' | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'center' | Dialog position |
Events
| Event | Description |
|---|---|
update:modelValue | Value changed |
confirm | Confirm clicked |
cancel | Cancel clicked |
close | Dialog closed |
opened | Enter transition finished |
closed | Leave transition finished |
Slots
| Slot | Description |
|---|---|
default | Custom body content (replaces description) |
useConfirm API
ts
import { useConfirm } from 'veloxui'
const confirm = useConfirm()
// Returns Promise<void>
// Resolves on confirm, rejects on cancel
confirm(options: ConfirmOptions | string): Promise<void>
// Type shortcuts
confirm.info(options)
confirm.success(options)
confirm.warning(options)
confirm.danger(options)ConfirmOptions
| Option | Type | Default | Description |
|---|---|---|---|
title | string | '' | Dialog title |
description | string | '' | Description text |
type | 'info' | 'success' | 'warning' | 'danger' | 'info' | Dialog type |
size | 'sm' | 'md' | 'lg' | 'sm' | Dialog width |
icon | boolean | true | Show icon |
closable | boolean | false | Show X button |
confirmText | string | 'Confirm' | Confirm button |
cancelText | string | 'Cancel' | Cancel button |
showCancel | boolean | true | Show cancel |
persistent | boolean | true | Shake on backdrop |