English | 中文 | 🎨 CSS Styling System
A lightweight, flexible Vue 3 modal component library built with TypeScript and designed for modern Vue applications.
- 🚀 Vue 3 Composition API - Built with modern Vue 3 and TypeScript
- 🎨 Flexible Styling - Works with any CSS framework (Tailwind CSS, etc.)
- 📱 Responsive Design - Mobile-friendly with touch support
- 🔧 Multiple Usage Patterns - Component-based and programmatic API
- 🎭 Rich Animations - Smooth enter/exit animations with customizable effects
- 🖱️ Draggable Support - Built-in support for draggable modals with boundary constraints
- 🔒 Focus Management - Automatic focus trapping and restoration
- ⌨️ Keyboard Support - ESC key to close, tab navigation
- 🌙 Theme Support - Compatible with dark/light mode themes
- 📦 Lightweight - Minimal bundle size with tree-shaking support
- 🛡️ TypeScript - Full TypeScript support with type definitions
npm install v-modals
# or
yarn add v-modals
# or
pnpm add v-modals
<template>
<div>
<Button @click="showModal = true">Open Modal</Button>
<Modal v-model:open="showModal" title="Basic Modal">
<p>This is a basic modal example.</p>
<template #footer>
<Button @click="showModal = false">Close</Button>
</template>
</Modal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Modal } from 'v-modals'
const showModal = ref(false)
</script>
The modal component requires CSS styles to display properly. Please refer to the CSS Styling System section at the end of this document for a complete style file example.
import { Modal } from 'v-modals'
// Info dialog
Modal.info({
title: 'Information',
content: 'This is an info message.',
onOk: () => console.log('OK clicked')
})
// Confirm dialog
Modal.confirm({
title: 'Confirm Action',
content: 'Are you sure you want to proceed?',
onOk: () => console.log('Confirmed'),
onCancel: () => console.log('Cancelled')
})
// Success dialog
Modal.success({
title: 'Success',
content: 'Operation completed successfully!'
})
// Error dialog
Modal.error({
title: 'Error',
content: 'Something went wrong.'
})
// Warning dialog
Modal.warning({
title: 'Warning',
content: 'Please check your input.'
})
import { createVNode } from 'vue'
import { Modal } from 'v-modals'
// Complex content with createVNode
Modal.info({
title: 'Rich Content Modal',
content: createVNode('div', { class: 'space-y-4' }, [
createVNode('h3', { class: 'text-lg font-semibold' }, 'Product Information'),
createVNode('div', { class: 'grid grid-cols-2 gap-4' }, [
createVNode('div', { class: 'p-3 border rounded' }, [
createVNode('h4', { class: 'font-medium' }, 'Features'),
createVNode('ul', { class: 'list-disc list-inside' }, [
createVNode('li', null, 'High Performance'),
createVNode('li', null, 'Easy to Use'),
createVNode('li', null, 'Extensible')
])
]),
createVNode('div', { class: 'p-3 border rounded' }, [
createVNode('h4', { class: 'font-medium' }, 'Specifications'),
createVNode('p', null, 'Version: 2.0.0'),
createVNode('p', null, 'Size: 15KB')
])
])
]),
width: 600
})
// Interactive content with event handlers
Modal.confirm({
title: 'Interactive Form',
content: createVNode('form', { class: 'space-y-4' }, [
createVNode('div', null, [
createVNode('label', { class: 'block text-sm font-medium' }, 'Name'),
createVNode('input', {
type: 'text',
class: 'w-full px-3 py-2 border rounded',
placeholder: 'Enter your name'
})
]),
createVNode('div', null, [
createVNode('label', { class: 'block text-sm font-medium' }, 'Email'),
createVNode('input', {
type: 'email',
class: 'w-full px-3 py-2 border rounded',
placeholder: 'Enter your email'
})
])
]),
onOk: () => {
// Handle form submission
console.log('Form submitted')
}
})
// Async operation with loading
Modal.confirm({
title: 'Save Changes',
content: 'Do you want to save your changes?',
onOk: async () => {
try {
// Show loading state
const loadingModal = Modal.info({
title: 'Saving...',
content: 'Please wait while we save your changes.',
closable: false,
maskClosable: false
})
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 2000))
// Close loading modal
loadingModal.destroy()
// Show success
Modal.success({
title: 'Saved Successfully',
content: 'Your changes have been saved.'
})
} catch (error) {
Modal.error({
title: 'Save Failed',
content: 'Failed to save changes. Please try again.'
})
}
}
})
// Parent modal that opens child modal
Modal.info({
title: 'Parent Modal',
content: createVNode('div', { class: 'space-y-4' }, [
createVNode('p', null, 'This is the parent modal.'),
createVNode('button', {
class: 'px-4 py-2 bg-blue-500 text-white rounded',
onClick: () => {
Modal.confirm({
title: 'Child Modal',
content: 'This is a nested modal!',
onOk: () => console.log('Child confirmed'),
onCancel: () => console.log('Child cancelled')
})
}
}, 'Open Child Modal')
])
})
// Custom positioned modal
Modal.info({
title: 'Custom Modal',
content: 'This modal has custom positioning and size.',
width: 800,
centered: true
})
// Large modal with custom styling
Modal.confirm({
title: 'Large Confirmation',
content: 'This is a large modal for important confirmations.',
width: 900,
okText: 'Proceed',
cancelText: 'Go Back',
centered: true
})
// Large modal
Modal.info({
title: 'Large Modal',
content: 'This is a large modal for displaying more content.',
width: '90vw',
centered: true
})
<template>
<div>
<Button @click="showModal">Open Draggable Modal</Button>
<Button @click="resetPosition" class="ml-2">Reset Position</Button>
<Modal
v-model:open="open"
:wrap-style="{ overflow: 'hidden' }"
@ok="handleOk"
@after-close="handleAfterClose"
>
<div class="space-y-4">
<p>🎯 This is a draggable modal example.</p>
<p>📱 You can drag the title bar to move the modal.</p>
<p>🔒 The modal is constrained within the browser window.</p>
<p>💾 Position is preserved during dragging.</p>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium mb-2">Drag Info:</h4>
<div class="text-sm text-gray-600 space-y-1">
<div>Position: X: {{ Math.round(transformX) }}px, Y: {{ Math.round(transformY) }}px</div>
<div>Status: {{ isDragging ? 'Dragging' : 'Static' }}</div>
<div>Has Moved: {{ startedDrag ? 'Yes' : 'No' }}</div>
</div>
</div>
</div>
<template #title>
<div
ref="modalTitleRef"
class="w-full cursor-move select-none px-2 py-1 rounded transition-colors duration-200"
:class="{
'hover:bg-black/5': !isDragging,
'bg-blue-50 border border-dashed border-blue-300': isDragging
}"
>
<span class="flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="text-gray-500">
<path d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
</svg>
Draggable Modal (Drag Me)
</span>
</div>
</template>
<template #modalRender="{ originVNode }">
<div
:style="transformStyle"
:class="{ 'shadow-2xl': isDragging }"
>
<component :is="originVNode" />
</div>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref, computed, watch, watchEffect } from 'vue'
import { useDraggable } from '@vueuse/core'
import { Modal } from 'v-modals'
const open = ref(false)
const modalTitleRef = ref()
const showModal = () => {
open.value = true
}
const handleOk = () => {
open.value = false
}
const handleAfterClose = () => {
// Position is preserved after closing
console.log('Modal closed, position preserved')
}
const resetPosition = () => {
transformX.value = 0
transformY.value = 0
preTransformX.value = 0
preTransformY.value = 0
startedDrag.value = false
}
// VueUse draggable
const { x, y, isDragging } = useDraggable(modalTitleRef)
// Drag state management
const startX = ref(0)
const startY = ref(0)
const startedDrag = ref(false)
const transformX = ref(0)
const transformY = ref(0)
const preTransformX = ref(0)
const preTransformY = ref(0)
const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0 })
// Watch for drag start
watch([x, y], () => {
if (!startedDrag.value) {
startX.value = x.value
startY.value = y.value
const bodyRect = document.body.getBoundingClientRect()
// Boundary calculation considering modal size
dragRect.value.left = 0
dragRect.value.top = 0
dragRect.value.right = bodyRect.width - 520 // Assume modal width 520px
dragRect.value.bottom = bodyRect.height - 400 // Assume modal height ~400px
preTransformX.value = transformX.value
preTransformY.value = transformY.value
}
startedDrag.value = true
})
// Watch for drag end
watch(isDragging, (dragging) => {
if (!dragging && startedDrag.value) {
startedDrag.value = false
}
})
// Calculate transform with boundary constraints
watchEffect(() => {
if (startedDrag.value) {
transformX.value =
preTransformX.value +
Math.min(Math.max(dragRect.value.left, x.value), dragRect.value.right) -
startX.value
transformY.value =
preTransformY.value +
Math.min(Math.max(dragRect.value.top, y.value), dragRect.value.bottom) -
startY.value
}
})
// Transform style with no transition during drag
const transformStyle = computed(() => {
return {
transform: `translate(${transformX.value}px, ${transformY.value}px)`,
transition: isDragging.value ? 'none' : 'transform 0.2s ease-out',
}
})
</script>
Prop | Type | Default | Description |
---|---|---|---|
open |
boolean |
false |
Controls modal visibility |
title |
string | VNode |
- | Modal title |
width |
string | number |
520 |
Modal width |
centered |
boolean |
false |
Center modal vertically |
closable |
boolean |
true |
Show close button |
closeIcon |
string | VNode |
- | Custom close icon |
mask |
boolean |
true |
Show background mask |
maskClosable |
boolean |
true |
Close modal when clicking mask |
keyboard |
boolean |
true |
Close modal with ESC key |
destroyOnClose |
boolean |
false |
Destroy modal content when closed |
confirmLoading |
boolean |
false |
Show loading on confirm button |
okText |
string | VNode |
- | OK button text |
cancelText |
string | VNode |
- | Cancel button text |
okType |
ButtonType |
- | OK button type |
okButtonProps |
ButtonProps |
- | OK button props |
cancelButtonProps |
ButtonProps |
- | Cancel button props |
zIndex |
number |
- | Modal z-index |
forceRender |
boolean |
false |
Force render modal content |
getContainer |
string | HTMLElement | (() => HTMLElement) | false |
- | Container for modal |
maskStyle |
CSSProperties |
- | Style for mask |
bodyStyle |
CSSProperties |
- | Style for modal body |
wrapClassName |
string |
- | Wrapper class name |
style |
CSSProperties | string |
- | Modal style |
transitionName |
string |
- | Transition name |
maskTransitionName |
string |
- | Mask transition name |
focusTriggerAfterClose |
boolean |
true |
Focus trigger after close |
modalRender |
(originVNode: VNode) => VNode |
- | Custom modal render function |
Event | Parameters | Description |
---|---|---|
update:open |
(open: boolean) |
Emitted when modal visibility changes |
ok |
(e: Event) |
Emitted when OK button is clicked |
cancel |
(e: Event) |
Emitted when Cancel button is clicked |
afterClose |
() |
Emitted after modal is completely closed |
Slot | Description |
---|---|
default |
Modal content |
title |
Custom title content |
footer |
Custom footer content |
header |
Custom header content |
modalRender |
Custom modal wrapper (for draggable functionality) |
Method | Parameters | Description |
---|---|---|
Modal.info(options) |
ModalOptions |
Show info modal |
Modal.success(options) |
ModalOptions |
Show success modal |
Modal.error(options) |
ModalOptions |
Show error modal |
Modal.warning(options) |
ModalOptions |
Show warning modal |
Modal.confirm(options) |
ModalOptions |
Show confirm modal |
Modal.destroyAll() |
- | Destroy all modals |
interface ModalOptions {
title?: string | VNode
content?: string | VNode | (() => VNode)
width?: number | string
centered?: boolean
closable?: boolean
closeIcon?: string | VNode
mask?: boolean
maskClosable?: boolean
keyboard?: boolean
destroyOnClose?: boolean
type?: 'info' | 'success' | 'error' | 'warn' | 'warning' | 'confirm'
okText?: string | VNode
cancelText?: string | VNode
okType?: ButtonType
zIndex?: number
forceRender?: boolean
getContainer?: string | HTMLElement | (() => HTMLElement) | false | null
// Event handlers
onOk?: (...args: any[]) => any
onCancel?: (...args: any[]) => any
afterClose?: () => void
// Advanced options
autoFocusButton?: null | 'ok' | 'cancel'
okButtonProps?: ButtonProps
cancelButtonProps?: ButtonProps
class?: string
style?: CSSProperties | string
wrapClassName?: string
footer?: VNode
icon?: VNode
maskStyle?: CSSProperties
bodyStyle?: CSSProperties
transitionName?: string
maskTransitionName?: string
focusTriggerAfterClose?: boolean
modalRender?: (arg: { originVNode: VNode }) => VNode
mousePosition?: { x: number; y: number } | null
okCancel?: boolean
appContext?: any
}
All programmatic methods return an object with the following methods:
interface ModalInstance {
destroy(): void // Manually destroy the modal
update(options: Partial<ModalOptions>): void // Update modal options
}
The modal component is designed to work with your existing CSS framework. You need to include the modal styles in your project:
/* Import your modal styles */
@import 'path/to/your/modal.css';
The component uses CSS classes that you can customize:
.v-modals-*
- Modal container classes.simple-dialog-*
- Dialog-specific classes.v-modals-confirm-*
- Confirm dialog classes
<Modal
v-model:open="showModal"
transition-name="custom-modal"
>
<!-- Modal content -->
</Modal>
<style>
.custom-modal-enter-active,
.custom-modal-leave-active {
transition: all 0.3s ease;
}
.custom-modal-enter-from {
opacity: 0;
transform: scale(0.8) translateY(-50px);
}
.custom-modal-leave-to {
opacity: 0;
transform: scale(1.1) translateY(50px);
}
</style>
import { createVNode, ref } from 'vue'
const showFormModal = () => {
const formData = { name: '', email: '', message: '' }
Modal.confirm({
title: 'Contact Form',
content: createVNode('form', { class: 'space-y-4' }, [
createVNode('div', null, [
createVNode('label', { class: 'block text-sm font-medium' }, 'Name *'),
createVNode('input', {
type: 'text',
required: true,
class: 'w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500',
onInput: (e) => formData.name = e.target.value
})
]),
createVNode('div', null, [
createVNode('label', { class: 'block text-sm font-medium' }, 'Email *'),
createVNode('input', {
type: 'email',
required: true,
class: 'w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500',
onInput: (e) => formData.email = e.target.value
})
]),
createVNode('div', null, [
createVNode('label', { class: 'block text-sm font-medium' }, 'Message'),
createVNode('textarea', {
rows: 4,
class: 'w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500',
onInput: (e) => formData.message = e.target.value
})
])
]),
onOk: () => {
// Validate form
if (!formData.name || !formData.email) {
Modal.warning({
title: 'Validation Error',
content: 'Please fill in all required fields.'
})
return false // Prevent modal from closing
}
// Submit form
console.log('Form submitted:', formData)
Modal.success({
title: 'Success',
content: 'Your message has been sent successfully!'
})
}
})
}
const showProgressModal = () => {
let progress = 0
let modalInstance
const updateProgress = () => {
progress += 10
const content = createVNode('div', { class: 'space-y-4' }, [
createVNode('p', null, 'Processing your request...'),
createVNode('div', { class: 'w-full bg-gray-200 rounded-full h-2' }, [
createVNode('div', {
class: 'bg-blue-500 h-2 rounded-full transition-all duration-300',
style: `width: ${progress}%`
})
]),
createVNode('p', { class: 'text-sm text-gray-600' }, `${progress}% complete`)
])
if (modalInstance) {
modalInstance.update({ content })
}
if (progress < 100) {
setTimeout(updateProgress, 500)
} else {
setTimeout(() => {
modalInstance.destroy()
Modal.success({
title: 'Complete',
content: 'Processing completed successfully!'
})
}, 1000)
}
}
modalInstance = Modal.info({
title: 'Processing',
content: createVNode('div', { class: 'space-y-4' }, [
createVNode('p', null, 'Starting process...'),
createVNode('div', { class: 'w-full bg-gray-200 rounded-full h-2' }, [
createVNode('div', {
class: 'bg-blue-500 h-2 rounded-full',
style: 'width: 0%'
})
])
]),
closable: false,
maskClosable: false
})
setTimeout(updateProgress, 500)
}
const showWizardModal = () => {
// Step 1
Modal.confirm({
title: 'Setup Wizard - Step 1',
content: 'Welcome to the setup wizard. Do you want to continue?',
onOk: () => {
// Step 2
Modal.confirm({
title: 'Setup Wizard - Step 2',
content: 'Please configure your preferences.',
onOk: () => {
// Step 3
Modal.confirm({
title: 'Setup Wizard - Step 3',
content: 'Review your settings and confirm.',
onOk: () => {
Modal.success({
title: 'Setup Complete',
content: 'Your setup has been completed successfully!'
})
}
})
}
})
}
})
}
// Create a modal manager
class ModalManager {
private modals: Map<string, any> = new Map()
create(id: string, options: ModalOptions) {
const modal = Modal.info(options)
this.modals.set(id, modal)
return modal
}
destroy(id: string) {
const modal = this.modals.get(id)
if (modal) {
modal.destroy()
this.modals.delete(id)
}
}
destroyAll() {
this.modals.forEach(modal => modal.destroy())
this.modals.clear()
}
update(id: string, options: Partial<ModalOptions>) {
const modal = this.modals.get(id)
if (modal) {
modal.update(options)
}
}
}
// Usage
const modalManager = new ModalManager()
modalManager.create('loading', {
title: 'Loading',
content: 'Please wait...',
closable: false
})
// Later...
modalManager.destroy('loading')
- When
confirmLoading
istrue
, the modal automatically disables all close operations - This prevents users from accidentally closing the modal during async operations
- Make sure to set
confirmLoading
tofalse
after operations complete
- Exceptions in
onOk
andonCancel
callbacks are not automatically handled - It's recommended to add proper error handling logic in callbacks
- Use
confirmLoading
to manage async operation states
- External styles will override internal default styles
- Use
!important
to force override component internal styles - Recommended to customize themes through CSS variables
- Ensure you have created and imported CSS style file (refer to CSS Styling System)
- Check for CSS style conflicts
- Verify CSS variables are correctly defined
- Confirm Tailwind CSS configuration is correct (if using)
- Check CSS file path is correct
- Check if
open
orvisible
property is correctly set - Confirm there are no CSS style conflicts
- Check if
z-index
setting is appropriate
- Ensure
transitionName
corresponding CSS animation is defined - Check if other CSS is affecting animation effects
- Verify
mousePosition
is correctly passed
- Ensure
modalRender
function is correctly implemented - Check if drag target element is correctly bound
- Verify drag library (like @vueuse/core) is correctly installed
Property | Type | Default | Description |
---|---|---|---|
open |
boolean |
false |
Control modal visibility |
title |
string | VNode |
- | Modal title |
width |
string | number |
520 |
Modal width |
centered |
boolean |
false |
Vertically center modal |
confirmLoading |
boolean |
false |
Confirm button loading state |
okText |
string | VNode |
'OK' |
OK button text |
cancelText |
string | VNode |
'Cancel' |
Cancel button text |
okType |
ButtonType |
'primary' |
OK button type |
Property | Type | Description |
---|---|---|
style |
CSSProperties | string |
Modal styles |
bodyStyle |
CSSProperties |
Modal body styles |
maskStyle |
CSSProperties |
Mask styles |
wrapClassName |
string |
Wrapper class name |
zIndex |
number |
Modal z-index |
Property | Type | Default | Description |
---|---|---|---|
closable |
boolean |
true |
Show close button |
maskClosable |
boolean |
true |
Close on mask click |
keyboard |
boolean |
true |
Close on ESC key |
destroyOnClose |
boolean |
false |
Destroy content on close |
Property | Type | Description |
---|---|---|
modalRender |
(arg: { originVNode: VNode }) => VNode |
Custom render function (for drag functionality) |
mousePosition |
{ x: number; y: number } | null |
Mouse position for animation start point |
getContainer |
string | HTMLElement | (() => HTMLElement) | false |
Modal container |
Event | Parameters | Description |
---|---|---|
@ok |
(e: MouseEvent) |
OK button click |
@cancel |
(e?: MouseEvent) |
Cancel button click or close |
@update:open |
(open: boolean) |
Visibility state change |
@afterClose |
() |
Triggered after completely closed |
Slot | Description |
---|---|
default |
Modal content |
title |
Custom title |
footer |
Custom footer |
modalRender |
Custom wrapper (for drag functionality) |
// Basic methods
Modal.info({ title: 'Info', content: 'Content' })
Modal.success({ title: 'Success', content: 'Operation successful' })
Modal.error({ title: 'Error', content: 'Operation failed' })
Modal.warning({ title: 'Warning', content: 'Warning message' })
Modal.confirm({ title: 'Confirm', content: 'Are you sure?' })
Modal.destroyAll() // Destroy all modals
// useModal Hook
const [modal, contextHolder] = Modal.useModal()
// Returns: [modal methods object, component to render]
const modal = Modal.confirm({...})
modal.destroy() // Destroy modal
modal.update({...}) // Update configuration
interface ModalOptions {
// Basic configuration
title?: string | VNode // Title
content?: string | VNode // Content
width?: string | number // Width
centered?: boolean // Center display
// Button configuration
okText?: string | VNode // OK button text
cancelText?: string | VNode // Cancel button text
okType?: 'primary' | 'danger' // OK button type
// Style configuration
style?: CSSProperties // Custom styles
maskStyle?: CSSProperties // Mask styles
bodyStyle?: CSSProperties // Body styles
// Event handling
onOk?: () => void | Promise<void> // OK callback
onCancel?: () => void // Cancel callback
afterClose?: () => void // After close callback
}
<Modal
v-model:open="open"
title="Custom Style"
:style="{
top: '50px',
border: '2px solid #1890ff',
borderRadius: '12px',
boxShadow: '0 10px 30px rgba(0,0,0,0.3)'
}"
>
<p>Custom border, border radius and shadow effects</p>
</Modal>
<Modal
v-model:open="open"
title="Mask Style"
:mask-style="{
backgroundColor: 'rgba(255, 0, 0, 0.3)',
backdropFilter: 'blur(5px)'
}"
>
<p>Red semi-transparent mask + background blur effect</p>
</Modal>
<Modal
v-model:open="open"
title="Body Style"
:body-style="{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
padding: '24px',
borderRadius: '8px'
}"
>
<p>Gradient background + white text + border radius effect</p>
</Modal>
<template>
<Modal
v-model:open="open"
title="Wrapper Class"
wrap-class-name="custom-modal-wrap"
>
<p>Complex style customization through CSS class names</p>
</Modal>
</template>
<style>
.custom-modal-wrap {
animation: customAnimation 0.3s ease-out;
}
.custom-modal-wrap .simple-dialog {
border: 2px dashed #722ed1;
border-radius: 16px;
}
</style>
<Modal
v-model:open="open"
title="High Z-Index Modal"
:z-index="2000"
:style="{ border: '3px solid #52c41a' }"
>
<p>Set high z-index to ensure above other elements</p>
</Modal>
<Modal v-model:open="open" title="Title" @ok="handleOk">
<p>Content</p>
</Modal>
Modal.confirm({
title: 'Confirm',
content: 'Are you sure you want to proceed?',
onOk: () => console.log('Confirmed')
})
<script setup>
const [modal, contextHolder] = Modal.useModal()
modal.info({ title: 'Info', content: 'Message content' })
</script>
<template>
<component :is="contextHolder" />
</template>
<Modal :confirm-loading="loading" @ok="handleSubmit">
<!-- Automatically disables all close operations when loading -->
</Modal>
<Modal :mouse-position="{ x: 100, y: 100 }">
<!-- Start scale animation from specified position -->
</Modal>
<Modal :modal-render="customRender">
<!-- Implement drag through modalRender -->
</Modal>
一个轻量级、灵活的 Vue 3 模态框组件库,使用 TypeScript 构建,专为现代 Vue 应用设计。
- 🚀 Vue 3 组合式 API - 使用现代 Vue 3 和 TypeScript 构建
- 🎨 灵活样式 - 兼容任何 CSS 框架(Tailwind CSS 等)
- 📱 响应式设计 - 移动端友好,支持触摸操作
- 🔧 多种使用方式 - 组件式和编程式 API
- 🎭 丰富动画 - 流畅的进入/退出动画,可自定义效果
- 🖱️ 拖拽支持 - 内置可拖拽模态框支持,带边界限制
- 🔒 焦点管理 - 自动焦点捕获和恢复
- ⌨️ 键盘支持 - ESC 键关闭,Tab 键导航
- 🌙 主题支持 - 兼容深色/浅色模式主题
- 📦 轻量级 - 最小打包体积,支持 tree-shaking
- 🛡️ TypeScript - 完整的 TypeScript 支持和类型定义
npm install v-modals
# 或
yarn add v-modals
# 或
pnpm add v-modals
<template>
<div>
<Button @click="showModal = true">打开模态框</Button>
<Modal v-model:open="showModal" title="基础模态框">
<p>这是一个基础模态框示例。</p>
<template #footer>
<Button @click="showModal = false">关闭</Button>
</template>
</Modal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Modal } from 'v-modals'
const showModal = ref(false)
</script>
- 确保已创建并引入 CSS 样式文件(参考 CSS 样式系统)
- 检查是否有 CSS 样式冲突
- 验证 CSS 变量是否正确定义
- 确认 Tailwind CSS 配置正确(如果使用)
- 检查 CSS 文件路径是否正确
- 检查
open
或visible
属性是否正确设置 - 确认没有 CSS 样式冲突
- 检查
z-index
设置是否合适
- 确保
transitionName
对应的 CSS 动画已定义 - 检查是否有其他 CSS 影响了动画效果
- 验证
mousePosition
是否正确传递
- 确保
modalRender
函数正确实现 - 检查拖拽目标元素是否正确绑定
- 验证拖拽库(如 @vueuse/core)是否正确安装
以下是所有已验证可用的模态框选项和功能:
-
open
/visible
- 显示控制 -
title
- 标题设置 -
width
- 宽度控制 -
centered
- 居中显示 -
closable
- 关闭按钮控制 -
closeIcon
- 自定义关闭图标 -
mask
/maskClosable
- 遮罩控制 -
keyboard
- 键盘支持 -
confirmLoading
- 加载状态(自动禁用关闭操作)
-
okText
/cancelText
- 按钮文本 -
okType
- 按钮类型(primary, danger 等) -
okButtonProps
/cancelButtonProps
- 按钮属性
-
style
- 自定义样式(如style="top: 20px"
) -
wrapClassName
- 包装器类名 -
maskStyle
/bodyStyle
- 遮罩和主体样式 -
zIndex
- 层级控制
-
modalRender
- 自定义渲染(拖拽功能) -
mousePosition
- 鼠标位置动画 -
transitionName
/maskTransitionName
- 自定义动画 -
getContainer
- 自定义容器 -
destroyOnClose
- 关闭时销毁
-
Modal.info()
- 信息对话框 -
Modal.success()
- 成功对话框 -
Modal.error()
- 错误对话框 -
Modal.warning()
/Modal.warn()
- 警告对话框 -
Modal.confirm()
- 确认对话框 -
Modal.destroyAll()
- 销毁所有模态框 -
Modal.useModal()
- Hook 版本
-
@ok
/@cancel
- 按钮点击事件 -
@update:open
- 状态更新事件 -
@change
- 状态改变事件 -
@after-close
- 关闭后事件
-
default
- 默认内容插槽 -
title
- 标题插槽 -
footer
- 底部插槽 -
closeIcon
- 关闭图标插槽 -
modalRender
- 自定义渲染插槽
- ES Module: ~25KB
- UMD: ~18KB
- TypeScript: Full type definitions included
- Tree-shaking: Supported
一个轻量级、灵活的 Vue 3 模态框组件库,使用 TypeScript 构建,专为现代 Vue 应用设计。
- 🚀 Vue 3 组合式 API - 使用现代 Vue 3 和 TypeScript 构建
- 🎨 灵活样式 - 兼容任何 CSS 框架(Tailwind CSS 等)
- 📱 响应式设计 - 移动端友好,支持触摸操作
- 🔧 多种使用方式 - 组件式和编程式 API
- 🎭 丰富动画 - 流畅的进入/退出动画,可自定义效果
- 🖱️ 拖拽支持 - 内置可拖拽模态框支持,带边界限制
- 🔒 焦点管理 - 自动焦点捕获和恢复
- ⌨️ 键盘支持 - ESC 键关闭,Tab 键导航
- 🌙 主题支持 - 兼容深色/浅色模式主题
- 📦 轻量级 - 最小打包体积,支持 tree-shaking
- 🛡️ TypeScript - 完整的 TypeScript 支持和类型定义
npm install v-modals
# 或
yarn add v-modals
# 或
pnpm add v-modals
<template>
<div>
<Button @click="showModal = true">打开模态框</Button>
<Modal v-model:open="showModal" title="基础模态框">
<p>这是一个基础模态框示例。</p>
<template #footer>
<Button @click="showModal = false">关闭</Button>
</template>
</Modal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Modal } from 'v-modals'
const showModal = ref(false)
</script>
模态框组件需要 CSS 样式才能正常显示。请参考文档末尾的 CSS 样式系统 部分获取完整的样式文件示例。
import { Modal } from 'v-modals'
// 信息对话框
Modal.info({
title: '信息',
content: '这是一条信息消息。',
onOk: () => console.log('确定被点击')
})
// 确认对话框
Modal.confirm({
title: '确认操作',
content: '您确定要继续吗?',
onOk: () => console.log('已确认'),
onCancel: () => console.log('已取消')
})
// 成功对话框
Modal.success({
title: '成功',
content: '操作成功完成!'
})
// 错误对话框
Modal.error({
title: '错误',
content: '出现了一些问题。'
})
// 警告对话框
Modal.warning({
title: '警告',
content: '请检查您的输入。'
})
import { createVNode } from 'vue'
import { Modal } from 'v-modals'
// 使用 createVNode 创建复杂内容
Modal.info({
title: '产品信息',
content: createVNode('div', { class: 'space-y-4' }, [
createVNode('h3', { class: 'text-lg font-semibold' }, '产品详情'),
createVNode('div', { class: 'grid grid-cols-2 gap-4' }, [
createVNode('div', { class: 'p-3 border rounded' }, [
createVNode('h4', { class: 'font-medium' }, '特性'),
createVNode('ul', { class: 'list-disc list-inside' }, [
createVNode('li', null, '高性能'),
createVNode('li', null, '易于使用'),
createVNode('li', null, '可扩展')
])
]),
createVNode('div', { class: 'p-3 border rounded' }, [
createVNode('h4', { class: 'font-medium' }, '规格'),
createVNode('p', null, '版本: 2.0.0'),
createVNode('p', null, '大小: 15KB')
])
])
]),
width: 600
})
// 带事件处理的交互式内容
Modal.confirm({
title: '交互式表单',
content: createVNode('form', { class: 'space-y-4' }, [
createVNode('div', null, [
createVNode('label', { class: 'block text-sm font-medium' }, '姓名'),
createVNode('input', {
type: 'text',
class: 'w-full px-3 py-2 border rounded',
placeholder: '请输入您的姓名'
})
]),
createVNode('div', null, [
createVNode('label', { class: 'block text-sm font-medium' }, '邮箱'),
createVNode('input', {
type: 'email',
class: 'w-full px-3 py-2 border rounded',
placeholder: '请输入您的邮箱'
})
])
]),
onOk: () => {
// 处理表单提交
console.log('表单已提交')
}
})
// 带加载状态的异步操作
Modal.confirm({
title: '保存更改',
content: '您要保存更改吗?',
onOk: async () => {
try {
// 显示加载状态
const loadingModal = Modal.info({
title: '保存中...',
content: '请稍候,正在保存您的更改。',
closable: false,
maskClosable: false
})
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 2000))
// 关闭加载模态框
loadingModal.destroy()
// 显示成功消息
Modal.success({
title: '保存成功',
content: '您的更改已保存。'
})
} catch (error) {
Modal.error({
title: '保存失败',
content: '保存更改失败,请重试。'
})
}
}
})
// 打开子模态框的父模态框
Modal.info({
title: '父模态框',
content: createVNode('div', { class: 'space-y-4' }, [
createVNode('p', null, '这是父模态框。'),
createVNode('button', {
class: 'px-4 py-2 bg-blue-500 text-white rounded',
onClick: () => {
Modal.confirm({
title: '子模态框',
content: '这是一个嵌套的模态框!',
onOk: () => console.log('子模态框确认'),
onCancel: () => console.log('子模态框取消')
})
}
}, '打开子模态框')
])
})
<template>
<div>
<Button @click="showModal">打开可拖拽模态框</Button>
<Button @click="resetPosition" class="ml-2">重置位置</Button>
<Modal
v-model:open="open"
:wrap-style="{ overflow: 'hidden' }"
@ok="handleOk"
@after-close="handleAfterClose"
>
<div class="space-y-4">
<p>🎯 这是一个可拖拽的模态框示例。</p>
<p>📱 您可以通过拖拽标题栏来移动模态框。</p>
<p>🔒 模态框会被限制在浏览器窗口内。</p>
<p>💾 位置会在拖拽过程中保持记忆。</p>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium mb-2">拖拽信息:</h4>
<div class="text-sm text-gray-600 space-y-1">
<div>当前位置: X: {{ Math.round(transformX) }}px, Y: {{ Math.round(transformY) }}px</div>
<div>拖拽状态: {{ isDragging ? '拖拽中' : '静止' }}</div>
<div>已拖拽: {{ startedDrag ? '是' : '否' }}</div>
</div>
</div>
</div>
<template #title>
<div
ref="modalTitleRef"
class="w-full cursor-move select-none px-2 py-1 rounded transition-colors duration-200"
:class="{
'hover:bg-black/5': !isDragging,
'bg-blue-50 border border-dashed border-blue-300': isDragging
}"
>
<span class="flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="text-gray-500">
<path d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
</svg>
可拖拽模态框 (拖拽我)
</span>
</div>
</template>
<template #modalRender="{ originVNode }">
<div
:style="transformStyle"
:class="{ 'shadow-2xl': isDragging }"
>
<component :is="originVNode" />
</div>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref, computed, watch, watchEffect } from 'vue'
import { useDraggable } from '@vueuse/core'
import { Modal } from 'v-modals'
const open = ref(false)
const modalTitleRef = ref()
const showModal = () => {
open.value = true
}
const handleOk = () => {
open.value = false
}
const handleAfterClose = () => {
// 关闭后保持位置
console.log('模态框已关闭,位置已保留')
}
const resetPosition = () => {
transformX.value = 0
transformY.value = 0
preTransformX.value = 0
preTransformY.value = 0
startedDrag.value = false
}
// VueUse 拖拽功能
const { x, y, isDragging } = useDraggable(modalTitleRef)
// 拖拽状态管理
const startX = ref(0)
const startY = ref(0)
const startedDrag = ref(false)
const transformX = ref(0)
const transformY = ref(0)
const preTransformX = ref(0)
const preTransformY = ref(0)
const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0 })
// 监听拖拽开始
watch([x, y], () => {
if (!startedDrag.value) {
startX.value = x.value
startY.value = y.value
const bodyRect = document.body.getBoundingClientRect()
// 边界计算,考虑模态框大小
dragRect.value.left = 0
dragRect.value.top = 0
dragRect.value.right = bodyRect.width - 520 // 假设模态框宽度 520px
dragRect.value.bottom = bodyRect.height - 400 // 假设模态框高度约 400px
preTransformX.value = transformX.value
preTransformY.value = transformY.value
}
startedDrag.value = true
})
// 监听拖拽结束
watch(isDragging, (dragging) => {
if (!dragging && startedDrag.value) {
startedDrag.value = false
}
})
// 计算带边界限制的变换
watchEffect(() => {
if (startedDrag.value) {
transformX.value =
preTransformX.value +
Math.min(Math.max(dragRect.value.left, x.value), dragRect.value.right) -
startX.value
transformY.value =
preTransformY.value +
Math.min(Math.max(dragRect.value.top, y.value), dragRect.value.bottom) -
startY.value
}
})
// 拖拽时无过渡效果的变换样式
const transformStyle = computed(() => {
return {
transform: `translate(${transformX.value}px, ${transformY.value}px)`,
transition: isDragging.value ? 'none' : 'transform 0.2s ease-out',
}
})
</script>
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
open |
boolean |
false |
控制模态框显示/隐藏 |
title |
string | VNode |
- | 模态框标题 |
width |
string | number |
520 |
模态框宽度 |
centered |
boolean |
false |
垂直居中显示 |
confirmLoading |
boolean |
false |
确认按钮加载状态 |
okText |
string | VNode |
'确定' |
确定按钮文本 |
cancelText |
string | VNode |
'取消' |
取消按钮文本 |
okType |
ButtonType |
'primary' |
确定按钮类型 |
属性 | 类型 | 描述 |
---|---|---|
style |
CSSProperties | string |
模态框样式 |
bodyStyle |
CSSProperties |
模态框主体样式 |
maskStyle |
CSSProperties |
遮罩样式 |
wrapClassName |
string |
包装器类名 |
zIndex |
number |
模态框层级 |
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
closable |
boolean |
true |
显示关闭按钮 |
maskClosable |
boolean |
true |
点击遮罩层关闭 |
keyboard |
boolean |
true |
ESC 键关闭 |
destroyOnClose |
boolean |
false |
关闭时销毁内容 |
属性 | 类型 | 描述 |
---|---|---|
modalRender |
(arg: { originVNode: VNode }) => VNode |
自定义渲染函数(拖拽功能) |
mousePosition |
{ x: number; y: number } | null |
鼠标位置动画起始点 |
getContainer |
string | HTMLElement | (() => HTMLElement) | false |
模态框容器 |
事件 | 参数 | 描述 |
---|---|---|
@ok |
(e: MouseEvent) |
点击确定按钮 |
@cancel |
(e?: MouseEvent) |
点击取消按钮或关闭 |
@update:open |
(open: boolean) |
显示状态改变 |
@afterClose |
() |
完全关闭后触发 |
插槽 | 描述 |
---|---|
default |
模态框内容 |
title |
自定义标题 |
footer |
自定义底部 |
modalRender |
自定义包装器(拖拽功能) |
// 基础方法
Modal.info({ title: '信息', content: '内容' })
Modal.success({ title: '成功', content: '操作成功' })
Modal.error({ title: '错误', content: '操作失败' })
Modal.warning({ title: '警告', content: '注意事项' })
Modal.confirm({ title: '确认', content: '确定要执行吗?' })
Modal.destroyAll() // 销毁所有模态框
// useModal Hook
const [modal, contextHolder] = Modal.useModal()
// 返回:[modal方法对象, 需要渲染的组件]
interface ModalOptions {
// 基础配置
title?: string | VNode // 标题
content?: string | VNode // 内容
width?: string | number // 宽度
centered?: boolean // 居中显示
// 按钮配置
okText?: string | VNode // 确定按钮文本
cancelText?: string | VNode // 取消按钮文本
okType?: 'primary' | 'danger' // 确定按钮类型
// 样式配置
style?: CSSProperties // 自定义样式
maskStyle?: CSSProperties // 遮罩样式
bodyStyle?: CSSProperties // 主体样式
// 事件处理
onOk?: () => void | Promise<void> // 确定回调
onCancel?: () => void // 取消回调
afterClose?: () => void // 关闭后回调
}
const modal = Modal.confirm({...})
modal.destroy() // 销毁模态框
modal.update({...}) // 更新配置
<Modal :confirm-loading="loading" @ok="handleSubmit">
<!-- 加载时自动禁用所有关闭操作 -->
</Modal>
<Modal :mouse-position="{ x: 100, y: 100 }">
<!-- 从指定位置开始缩放动画 -->
</Modal>
<Modal :modal-render="customRender">
<!-- 通过 modalRender 实现拖拽 -->
</Modal>
<Modal
v-model:open="open"
title="自定义样式"
:style="{
top: '50px',
border: '2px solid #1890ff',
borderRadius: '12px',
boxShadow: '0 10px 30px rgba(0,0,0,0.3)'
}"
>
<p>自定义边框、圆角和阴影效果</p>
</Modal>
<Modal
v-model:open="open"
title="遮罩样式"
:mask-style="{
backgroundColor: 'rgba(255, 0, 0, 0.3)',
backdropFilter: 'blur(5px)'
}"
>
<p>红色半透明遮罩 + 背景模糊效果</p>
</Modal>
<Modal
v-model:open="open"
title="主体样式"
:body-style="{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
padding: '24px',
borderRadius: '8px'
}"
>
<p>渐变背景 + 白色文字 + 圆角效果</p>
</Modal>
<template>
<Modal
v-model:open="open"
title="包装器类名"
wrap-class-name="custom-modal-wrap"
>
<p>通过CSS类名进行复杂样式定制</p>
</Modal>
</template>
<style>
.custom-modal-wrap {
animation: customAnimation 0.3s ease-out;
}
.custom-modal-wrap .simple-dialog {
border: 2px dashed #722ed1;
border-radius: 16px;
}
</style>
<Modal
v-model:open="open"
title="高层级模态框"
:z-index="2000"
:style="{ border: '3px solid #52c41a' }"
>
<p>设置高层级确保在其他元素之上</p>
</Modal>
<Modal v-model:open="open" title="标题" @ok="handleOk">
<p>内容</p>
</Modal>
Modal.confirm({
title: '确认',
content: '确定要执行此操作吗?',
onOk: () => console.log('确认')
})
<script setup>
const [modal, contextHolder] = Modal.useModal()
modal.info({ title: '信息', content: '消息内容' })
</script>
<template>
<component :is="contextHolder" />
</template>
- ES 模块: ~25KB
- UMD: ~18KB
- TypeScript: 包含完整类型定义
- Tree-shaking: 支持
Here's a complete modal.css
style file example that you can use as a reference to create your own modal styles:
以下是一个完整的 modal.css
样式文件示例,你可以参考这个文件来创建自己的模态框样式:
/* Simple Modal CSS - Standalone Modal Style File */
/* Simple Modal CSS - 独立的 Modal 样式文件 */
/* This file contains all Modal related styles using Tailwind CSS classes */
/* 这个文件包含所有 Modal 相关的样式,使用 Tailwind CSS 类 */
@import "tailwindcss";
@import "tw-animate-css";
/* Modal Base Styles | Modal 基础样式 */
.simple-modal-root,
.simple-dialog-root {
@apply relative;
}
/* Mask Layer | 遮罩层 */
.simple-modal-mask,
.simple-dialog-mask {
@apply fixed inset-0 z-[50] bg-black/50;
}
/* Modal Container | Modal 容器 */
.simple-modal-wrap,
.simple-dialog-wrap {
@apply fixed inset-0 overflow-auto outline-none z-[50];
-webkit-overflow-scrolling: touch;
}
/* Centered Layout | 居中布局 */
.simple-modal-wrap.simple-modal-centered,
.simple-dialog-wrap.simple-dialog-centered {
@apply text-center;
}
.simple-modal-wrap.simple-modal-centered::before,
.simple-dialog-wrap.simple-dialog-centered::before {
@apply inline-block w-0 h-full align-middle;
content: '';
}
/* Modal Body | Modal 主体 */
.simple-modal,
.simple-dialog {
position: relative;
top: var(--modal-top, 100px); /* Use CSS variable, default 100px | 使用 CSS 变量,默认 100px */
width: auto;
max-width: calc(100vw - 32px);
margin: 0 auto;
padding-bottom: 1.5rem;
pointer-events: none;
}
/* Modal Content Area | Modal 内容区域 */
.simple-modal-content,
.simple-dialog-content {
@apply relative bg-background border border-border rounded-lg shadow-lg p-6 text-base leading-relaxed pointer-events-auto;
}
/* Close Button | 关闭按钮 */
.simple-modal-close,
.simple-dialog-close {
position: absolute;
top: 1rem;
right: 1rem;
z-index: 10;
display: inline-flex;
height: 2rem;
width: 2rem;
align-items: center;
justify-content: center;
border: none;
background: transparent;
cursor: pointer;
color: var(--muted-foreground);
border-radius: 0.375rem;
transition: all 0.2s;
}
.simple-modal-close:hover,
.simple-dialog-close:hover {
background-color: var(--accent);
color: var(--accent-foreground);
}
/* Modal Header, Title, Content, Footer | Modal 头部、标题、内容、页脚 */
.simple-modal-header,
.simple-dialog-header {
margin-bottom: 0.5rem;
}
.simple-modal-title,
.simple-dialog-title {
margin: 0;
font-weight: 600;
font-size: 1.125rem;
line-height: 1.25;
color: var(--foreground);
}
.simple-modal-body,
.simple-dialog-body {
font-size: 1rem;
line-height: 1.625;
color: var(--muted-foreground);
}
.simple-modal-footer,
.simple-dialog-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
padding-top: 0.75rem;
}
/* Animation Effects | 动画效果 */
.simple-zoom-enter-active {
transition: all 0.3s cubic-bezier(0.08, 0.82, 0.17, 1);
}
.simple-zoom-leave-active {
transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
pointer-events: none;
}
.simple-zoom-enter-from,
.simple-zoom-leave-to {
opacity: 0;
transform: scale(0.2);
}
.simple-fade-enter-active,
.simple-fade-leave-active {
transition: opacity 0.3s;
}
.simple-fade-enter-from,
.simple-fade-leave-to {
opacity: 0;
}
/* Responsive Design | 响应式设计 */
@media (max-width: 767px) {
.simple-modal,
.simple-dialog {
max-width: calc(100vw - 16px);
margin: 8px auto;
}
}
/* Confirm Dialog Styles | Confirm Dialog 样式 */
.simple-modal-confirm-body {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.simple-modal-confirm-title {
flex: none;
display: block;
overflow: hidden;
color: var(--foreground);
font-weight: 600;
font-size: 1rem;
line-height: 1.25;
}
.simple-modal-confirm-content {
font-size: 1rem;
color: var(--muted-foreground);
}
/* Confirm Dialog Icon Colors | 确认框图标颜色 */
.simple-modal-confirm-error > svg {
color: var(--destructive) !important;
}
.simple-modal-confirm-warning > svg,
.simple-modal-confirm-confirm > svg {
color: #eab308 !important; /* yellow-500 */
}
.simple-modal-confirm-info > svg {
color: #3b82f6 !important; /* blue-500 */
}
.simple-modal-confirm-success > svg {
color: #22c55e !important; /* green-500 */
}
CSS Variable Support | CSS 变量支持:
--modal-top
: Controls modal distance from top, default 100px | 控制模态框距离顶部的距离,默认 100px- Theme variables support | 支持主题变量:
--background
,--foreground
,--muted-foreground
, etc.
Tailwind CSS Integration | Tailwind CSS 集成:
- Uses
@apply
directive to integrate Tailwind classes | 使用@apply
指令集成 Tailwind 类 - Supports responsive design and dark mode | 支持响应式设计和暗色模式
Animation System | 动画系统:
simple-zoom
: Scale animation effect | 缩放动画效果simple-fade
: Fade in/out effect | 淡入淡出效果
-
Copy the CSS code above to your project and save as
modal.css
复制上面的 CSS 代码到你的项目中,保存为modal.css
-
Import in your main style file | 在你的主样式文件中引入:
@import "./modal.css";
-
Customize as needed CSS variables and styles 根据需要自定义 CSS 变量和样式
-
Ensure Tailwind CSS is properly configured (if using) 确保 Tailwind CSS 正确配置(如果使用)
The modal uses the following CSS class structure for customization: 模态框使用以下 CSS 类名结构进行自定义:
.simple-modal-root
/.simple-dialog-root
- Root container | 根容器.simple-modal-mask
/.simple-dialog-mask
- Mask layer | 遮罩层.simple-modal-wrap
/.simple-dialog-wrap
- Modal wrapper | 模态框包装器.simple-modal
/.simple-dialog
- Modal body | 模态框主体.simple-modal-content
/.simple-dialog-content
- Content area | 内容区域.simple-modal-header
/.simple-dialog-header
- Header | 头部.simple-modal-title
/.simple-dialog-title
- Title | 标题.simple-modal-body
/.simple-dialog-body
- Body | 主体.simple-modal-footer
/.simple-dialog-footer
- Footer | 页脚.simple-modal-close
/.simple-dialog-close
- Close button | 关闭按钮
MIT License
Contributions are welcome! Please feel free to submit a Pull Request.
欢迎贡献!请随时提交 Pull Request。
If you encounter any issues while using this library, please submit an Issue or check the example project.
如果您在使用过程中遇到问题,请提交 Issue 或查看示例项目。
This project is inspired by and built upon the excellent work of:
本项目受到以下优秀项目的启发并基于其构建:
-
Ant Design Vue - A high-quality Vue UI library that provides the foundation and design patterns for this modal component. Their modal implementation served as the primary reference for API design and functionality.
-
Ant Design - The original React-based design system that established the design principles and interaction patterns that make this modal component intuitive and user-friendly.
We are grateful to the maintainers and contributors of these projects for their outstanding work in creating robust, accessible, and well-designed UI components that benefit the entire frontend development community.
我们感谢 Ant Design Vue 和 Ant Design 的维护者和贡献者们,感谢他们在创建强大、可访问且设计精良的 UI 组件方面所做的杰出工作,这些工作使整个前端开发社区受益。
Special thanks to:
- The Ant Design team for establishing the design language and interaction patterns
- The Ant Design Vue team for the Vue.js implementation and API design
- All contributors who have helped improve these libraries over the years
特别感谢:
- Ant Design 团队建立的设计语言和交互模式
- Ant Design Vue 团队的 Vue.js 实现和 API 设计
- 多年来帮助改进这些库的所有贡献者
This modal component aims to provide a lightweight, framework-agnostic solution while maintaining the familiar API and behavior patterns that developers love from Ant Design Vue.
这个模态框组件旨在提供一个轻量级、框架无关的解决方案,同时保持开发者喜爱的 Ant Design Vue 熟悉的 API 和行为模式。