Modal Component
The Modal
component is a versatile dialog component built with Vue 3 and Headless UI. It provides a customizable modal interface perfect for forms, alerts, or any content that needs to appear in a dialog.
Basic Usage
Import and use Modal
in your Vue components:
vue
<template>
<Modal
id="example-modal"
title="Example Modal"
:show="isOpen"
@modal:close="isOpen = false"
>
<p>Modal content goes here</p>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Modal } from '@robuust-digital/vue-components';
const isOpen = ref(false);
</script>
Props
Prop | Type | Default | Description |
---|---|---|---|
as | String | 'div' | Element type to render (div or form ) |
id | String | Required | Unique identifier for the modal |
title | String | Required | Title displayed in the modal header |
showClose | Boolean | false | Shows close button in header when true |
spinning | Boolean | false | Shows loading state in submit button |
submitLabel | String | 'Confirm' | Text for the submit button |
cancelLabel | String | 'Cancel' | Text for the cancel button |
panelClass | String | 'sm:rvc-max-w-lg' | CSS class for modal panel width |
Events
Event | Arguments | Description |
---|---|---|
modal:open | - | Emitted when modal starts opening |
modal:close | - | Emitted when modal close is requested |
modal:save | event | Emitted when form is submitted (if as="form" ) |
modal:closed | - | Emitted when Transition finishes |
Slots
Default Slot
The main content area of the modal.
vue
<template>
<Modal id="default-slot-modal" title="Example">
<p>Main content here</p>
</Modal>
</template>
Header Slot
Customize the header section:
vue
<template>
<Modal id="header-slot-modal" title="Example">
<template #header="{ title, showClose }">
<div class="custom-header">
<h2>{{ title }}</h2>
<button v-if="showClose" @click="$emit('modal:close')">Close</button>
</div>
</template>
</Modal>
</template>
Footer Slot
Customize the footer actions:
vue
<template>
<Modal id="footer-slot-modal" title="Example">
<template #footer="{ loading }">
<ButtonBase label="Custom Cancel" @click="$emit('modal:close')" />
<ButtonBase
type="submit"
label="Custom Confirm"
:spinning="loading"
/>
</template>
</Modal>
</template>
Examples
Basic Form Modal
vue
<template>
<ButtonBase label="Open Modal" @click="isOpen = true" />
<Modal
id="create-user-modal"
as="form"
title="Create User"
:show="isOpen"
@modal:close="isOpen = false"
@modal:save="handleSave"
>
<div class="space-y-4">
<input v-model="formData.name" type="text" placeholder="Name" required />
<input v-model="formData.email" type="email" placeholder="Email" required />
</div>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
const isOpen = ref(false);
const formData = ref({
name: '',
email: '',
});
const handleSave = (e) => {
console.log('Form submitted:', formData.value);
isOpen.value = false;
};
</script>
Custom Width Modal
vue
<template>
<Modal
id="wide-modal"
title="Wide Modal"
panel-class="max-w-2xl"
:show="isOpen"
@modal:close="isOpen = false"
>
<p>This modal uses a custom width class.</p>
</Modal>
</template>
Loading State
vue
<template>
<Modal
id="loading-modal"
title="Saving..."
:show="isOpen"
:spinning="isSaving"
@modal:close="isOpen = false"
@modal:save="handleSave"
>
<p>The footer submit button will show a spinner when spinning is true.</p>
</Modal>
</template>
Show Close Icon Modal
vue
<template>
<Modal
id="close-icon-modal"
title="Modal With Close Icon"
show-close
:show="isOpen"
@modal:close="isOpen = false"
>
<p>This modal has a close icon in the header. Clicking the icon will close the modal.</p>
</Modal>
</template>
Customization with Tailwind CSS
To customize the modal
styles globally, extend your Tailwind configuration. Example:
javascript
// tailwind.config.js
export default {
theme: {
extend: {
components: (theme) => ({
modal: {
// Available variables you can override:
'--rvc-modal-backdrop-bg-color': withAlphaValue(theme('colors.slate.900'), 0.5),
'--rvc-modal-border-color': theme('colors.slate.200'),
'--rvc-modal-border-width': theme('borderWidth.DEFAULT'),
'--rvc-modal-border-style': 'solid',
'--rvc-modal-border-radius': theme('borderRadius.DEFAULT'),
'--rvc-modal-box-shadow': theme('boxShadow.xl'),
'--rvc-modal-margin-y': 0,
'--rvc-modal-padding-x': theme('padding.4'),
'--rvc-modal-padding-y': theme('padding.4'),
'--rvc-modal-header-bg-color': theme('colors.slate.50'),
'--rvc-modal-title-font-size': theme('fontSize.xl'),
'--rvc-modal-title-font-weight': theme('fontWeight.bold'),
'--rvc-modal-title-color': theme('colors.slate.900'),
'--rvc-modal-close-size': theme('width.6'),
'--rvc-modal-close-color': theme('colors.slate.700'),
'--rvc-modal-close-color-hover': theme('colors.slate.900'),
'--rvc-modal-content-bg-color': theme('colors.white'),
'--rvc-modal-footer-bg-color': theme('colors.white'),
'--rvc-modal-footer-gap': theme('gap.2'),
// From 'sm' breakpoint and up
'screen-sm': {
'--rvc-modal-padding-x': theme('padding.12'),
'--rvc-modal-padding-y': theme('padding.12'),
},
// You can also customize elements with CSS
zIndex: 999,
'.modal-header': {
// Apply Tailwind classes
'@apply bg-white': {},
// or just CSS
backgroundColor: theme('colors.white'),
},
// ...
},
}),
},
},
};