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.

Installation

To use the Modal component you need to install the @headlessui/vue package.

bash
yarn add @robuust-digital/vue-components @headlessui/vue

Import the modal from dialog package

js
import { Modal } from '@robuust-digital/vue-components/dialogs';

Import CSS

css
@import "@robuust-digital/vue-components/dialogs/css";

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/dialogs';

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: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>

Title slot

Customize the title:

vue
<template>
  <Modal id="title-slot-modal" title="Example">
    <template #title="{ title, id, dialogTitle }">
      <div>
        <Component :is="dialogTitle" :id="id">
          {{ title }}
        </Component>
        <p class="text-sm font-light">
          Little custom subtitle
        </p>
      </div>
    </template>
  </Modal>
</template>

Close slot

Customize the close button:

vue
<template>
  <Modal id="close-modal-slot" title="Example">
    <template #close="{ icon, emitClose }">
      <button @click="emitClose" type="button">
        <Component :is="icon" aria-hidden="true" />
      </button>
    </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>

CSS Customization ⚡️

To customize the modal styles global

css
:root {
  /* Available variables */
  --rvc-modal-backdrop-bg-color: var(--rvc-dialog-backdrop-bg-color);
  --rvc-modal-border-color: var(--rvc-base-border-color);
  --rvc-modal-border-width: var(--rvc-base-border-width);
  --rvc-modal-border-style: var(--rvc-base-border-style);
  --rvc-modal-border-radius: var(--rvc-base-border-radius);
  --rvc-modal-padding-x: var(--rvc-dialog-padding-x);
  --rvc-modal-padding-y: var(--rvc-dialog-padding-y);
  --rvc-modal-header-bg-color: var(--rvc-dialog-header-bg-color);
  --rvc-modal-title-font-size: var(--rvc-dialog-title-font-size);
  --rvc-modal-title-font-weight: var(--rvc-dialog-title-font-weight);
  --rvc-modal-title-font-family: var(--rvc-dialog-title-font-family);
  --rvc-modal-title-color: var(--rvc-dialog-title-color);
  --rvc-modal-close-size: var(--rvc-dialog-close-size);
  --rvc-modal-close-color: var(--rvc-dialog-close-color);
  --rvc-modal-close-color-hover: var(--rvc-dialog-close-color-hover);
  --rvc-modal-content-bg-color: var(--rvc-dialog-content-bg-color);
  --rvc-modal-footer-bg-color: var(--rvc-dialog-footer-bg-color);
  --rvc-modal-footer-gap: var(--rvc-dialog-footer-gap);
  --rvc-modal-box-shadow: var(--box-shadow-xl);
  --rvc-modal-margin-y: 0;
}