Skip to content

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

PropTypeDefaultDescription
asString'div'Element type to render (div or form)
idStringRequiredUnique identifier for the modal
titleStringRequiredTitle displayed in the modal header
showCloseBooleanfalseShows close button in header when true
spinningBooleanfalseShows loading state in submit button
submitLabelString'Confirm'Text for the submit button
cancelLabelString'Cancel'Text for the cancel button
panelClassString'sm:rvc-max-w-lg'CSS class for modal panel width

Events

EventArgumentsDescription
modal:open-Emitted when modal starts opening
modal:close-Emitted when modal close is requested
modal:saveeventEmitted 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>

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'),
          },

          // ...
        },
      }),
    },
  },
};