Modal
A general-purpose overlay panel for forms, details, and complex content. Supports draggable, fullscreen, drawer mode, custom transitions, stacked modals, focus trap, auto-close, programmatic API, and more.
<script setup>
const show = ref(false)
</script>
<template>
<button @click="show = true">Open Modal</button>
<VxModal v-model="show" title="Basic Modal">
<p>This is a basic modal.</p>
</VxModal>
</template>With Description
<template>
<VxModal v-model="show" title="Edit Profile" description="Make changes here." />
</template>Sizes
<template>
<VxModal size="sm" />
<VxModal size="md" />
<VxModal size="lg" />
<VxModal size="xl" />
<VxModal size="full" />
</template>Custom Width
Set an arbitrary width with the width prop.
<template>
<VxModal v-model="show" :width="666" title="Custom Width" />
<VxModal v-model="show" width="80%" title="Percentage Width" />
</template>Draggable
Drag the modal by its header to reposition it.
<template>
<VxModal v-model="show" title="Drag Me" draggable />
</template>Fullscreen Toggle
A button in the header lets users expand the modal to fullscreen and back.
<template>
<VxModal v-model="show" title="Expand Me" fullscreenable />
</template>Before Close
An async guard that runs before closing. Return false to prevent it.
<script setup>
// Sync — native browser confirm
function beforeClose() {
return window.confirm('Discard changes?')
}
// Async — with useConfirm dialog
import { useConfirm } from 'veloxui'
const dialog = useConfirm()
async function beforeCloseAsync() {
// resolves = allow close, rejects = block close
await dialog.warning({
title: 'Discard Changes?',
confirmText: 'Discard',
cancelText: 'Keep Editing',
})
}
</script>
<template>
<VxModal v-model="show" :before-close="beforeClose" title="Guard" />
</template>Drawer Mode
Slide in from any edge. Use the drawer prop with left, right, top, or bottom.
<template>
<VxModal v-model="show" drawer="left" title="Left" />
<VxModal v-model="show" drawer="right" title="Right" />
<VxModal v-model="show" drawer="top" title="Top" />
<VxModal v-model="show" drawer="bottom" title="Bottom" />
<VxModal v-model="show" drawer="right" size="lg" title="Wide Drawer" />
</template>Custom Transitions
Choose an animation: zoom (default), fade, slide-up, slide-down, flip.
<template>
<VxModal transition="fade" />
<VxModal transition="slide-up" />
<VxModal transition="slide-down" />
<VxModal transition="flip" />
</template>Stacked Modals
Open modals on top of other modals. Each gets a higher z-index automatically.
<template>
<VxModal v-model="first" title="First">
<template #footer>
<button @click="second = true">Open Second</button>
</template>
</VxModal>
<VxModal v-model="second" title="Second" />
</template>Focus Trap
Enabled by default. Tab key cycles within the modal for accessibility.
<template>
<!-- Focus trap is enabled by default -->
<VxModal v-model="show" title="Focus Trap">
<input placeholder="Tab cycles here" />
</VxModal>
<!-- Disable it -->
<VxModal v-model="show" :focus-trap="false" />
</template>Auto Close
Automatically close after a duration (ms).
<template>
<VxModal v-model="show" :auto-close="3000" title="Closes in 3s" />
</template>Destroy on Close
By default, content is destroyed when the modal closes (destroy-on-close). Set it to false to keep content alive (preserves form state, scroll position, etc).
<template>
<VxModal v-model="show" :destroy-on-close="false" title="Keep Alive" />
</template>Persistent
Cannot be dismissed by overlay click or Escape. Shows a shake animation.
<template>
<VxModal v-model="show" persistent title="Persistent" />
</template>Position
Place the modal at any corner or edge of the viewport.
<template>
<VxModal v-model="show" position="top-left" title="Top Left" />
<VxModal v-model="show" position="top-center" title="Top Center" />
<VxModal v-model="show" position="top-right" title="Top Right" />
<VxModal v-model="show" position="center" title="Center (default)" />
<VxModal v-model="show" position="bottom-left" title="Bottom Left" />
<VxModal v-model="show" position="bottom-center" title="Bottom Center" />
<VxModal v-model="show" position="bottom-right" title="Bottom Right" />
</template>Centered vs Top
<template>
<VxModal v-model="show" :centered="false" title="Top" />
</template>Hide Overlay
<template>
<VxModal v-model="show" hide-overlay />
</template>Loading
<template>
<VxModal v-model="show" loading title="Processing..." />
</template>Scrollable Content
<template>
<VxModal v-model="show" title="Terms" size="sm">
<div v-for="i in 20" :key="i"><p>Long content...</p></div>
</VxModal>
</template>Custom Header & Footer
<template>
<VxModal v-model="show">
<template #header>Custom header</template>
<template #footer>
<button @click="show = false">Cancel</button>
<button @click="show = false">Confirm</button>
</template>
</VxModal>
</template>Programmatic API
Use the useModal() composable to open modals from JavaScript without writing template markup.
<script setup>
import { useModal } from 'veloxui'
const { open } = useModal()
function showModal() {
const instance = open({
title: 'Hello',
content: 'Created from JavaScript!',
size: 'sm',
})
// Close programmatically
// instance.close()
}
function showDrawer() {
open({
title: 'Drawer',
drawer: 'right',
content: 'A programmatic drawer.',
autoClose: 3000,
})
}
</script>Teleport Target
Mount the modal into a custom container instead of body.
<template>
<VxModal v-model="show" teleport-to="#my-container" />
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | false | Show/hide (v-model) |
title | string | '' | Header title |
description | string | '' | Subtitle below title |
size | 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'md' | Modal width |
width | string | number | — | Custom width (overrides size) |
closable | boolean | true | Show close button |
closeOnOverlay | boolean | true | Close on overlay click |
closeOnEsc | boolean | true | Close on Escape key |
centered | boolean | true | Vertically center |
persistent | boolean | false | Shake instead of close |
hideOverlay | boolean | false | Transparent backdrop |
loading | boolean | false | Show loading overlay |
draggable | boolean | false | Drag by header |
fullscreenable | boolean | false | Show fullscreen toggle |
beforeClose | () => boolean | Promise<boolean> | — | Guard before closing |
drawer | 'left' | 'right' | 'top' | 'bottom' | — | Drawer mode |
transition | 'zoom' | 'fade' | 'slide-up' | 'slide-down' | 'flip' | — | Animation |
focusTrap | boolean | true | Trap Tab within modal |
autoClose | number | 0 | Auto-close after ms |
destroyOnClose | boolean | true | Destroy content on close |
teleportTo | string | 'body' | Teleport target selector |
position | 'center' | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'center' | Modal position |
Events
| Event | Description |
|---|---|
update:modelValue | Value changed |
close | Close triggered |
opened | Enter transition finished |
closed | Leave transition finished |
Slots
| Slot | Description |
|---|---|
default | Body content |
header | Custom header |
footer | Footer actions (default: Close button) |
Exposed Methods
| Method | Description |
|---|---|
close() | Close the modal programmatically |
toggleFullscreen() | Toggle fullscreen state |