Modal

Accessible Modal component that follows the WAI-ARIA Dialog (Modal) Pattern. It is rendered within a Portal.

Installation

npm i vue-ari

or

yarn add vue-ari

Usage

<template>
  <ModalDisclosure v-bind="modal">
    Open Modal
  </ModalDisclosure>
  <ModalBackdrop v-bind="modal">
    <Modal v-bind="modal">
      Modal Content
    </Modal>
  </ModalBackdrop>
</template>

<script>
  import { Modal, ModalBackdrop, ModalDisclosure, useModalState } from 'vue-ari'

  export default {
    components: {
      Modal,
      ModalBackdrop,
      ModalDisclosure,
    },
    setup() {
      const modal = useModalState()
      return {
        modal,
      }
    },
  }
</script>

Styling

Ari components don't include styling by default. This gives you the ability to add styles however you like.

Example Using Tailwind

<template>
  <ModalDisclosure
    v-bind="modal"
    class="text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
  >
    Open Modal
  </ModalDisclosure>
  <ModalBackdrop
    v-bind="modal"
    class="fixed inset-0 flex items-center justify-center bg-opacity-75 bg-black"
  >
    <Modal
      v-bind="modal"
      class="max-w-xs rounded shadow-lg border border-solid border-gray-300 py-3 px-5 bg-white"
    >
      Modal Content
    </Modal>
  </ModalBackdrop>
</template>

<script>
  import { Modal, ModalDisclosure, useModalState } from 'vue-ari'

  export default {
    components: {
      Modal,
      ModalDisclosure,
    },
    setup() {
      const modal = useModalState()
      return {
        modal,
      }
    },
  }
</script>

Reusable Components

It would get pretty verbose to add the same styling classes wherever you like to use a Modal. So the recommended way is wrapping Ari components inside your own base components and use them inside your app.

Base component for disclosure:

<template>
  <ModalDisclosure
    v-bind="$props"
    class="text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
  >
    <slot />
  </ModalDisclosure>
</template>

<script>
  import { ModalDisclosure, modalDisclosureProps } from 'vue-ari'

  export default {
    name: 'AppModalDisclosure',
    props: modalDisclosureProps,
    components: {
      ModalDisclosure,
    },
  }
</script>

Base component for modal:

<template>
  <ModalBackdrop
    v-bind="$props"
    class="fixed inset-0 flex items-center justify-center bg-opacity-75 bg-black"
  >
    <Modal
      v-bind="{ ...$props, ...$attrs }"
      class="max-w-xs rounded shadow-lg border border-solid border-gray-300 py-3 px-5 bg-white"
    >
      <slot />
    </Modal>
  </ModalBackdrop>
</template>

<script>
  import { Modal, ModalBackdrop, modalProps } from 'vue-ari'

  export default {
    name: 'AppModal',
    props: modalProps,
    inheritAttrs: false,
    components: {
      Modal,
      ModalBackdrop,
    },
  }
</script>

Inside your app:

<template>
  <AppModalDisclosure v-bind="modal">
    Open Modal
  </AppModalDisclosure>
  <AppModal v-bind="modal">
    Modal Content
  </AppModal>
</template>

<script>
  import { useModalState } from 'vue-ari'
  import { AppModal, AppModalDisclosure } from './components'

  export default {
    components: {
      AppModal,
      AppModalDisclosure,
    },
    setup() {
      const modal = useModalState()
      return {
        modal,
      }
    },
  }
</script>

Abstracting State

If you would rather not create a modal state each time, just create a provider component.

Provider component:

<template>
  <slot />
</template>

<script>
  import { provide } from 'vue'
  import { useModalState } from 'vue-ari'

  export default {
    name: 'AppModalProvider',
    setup() {
      provide('modalState', useModalState())
    },
  }
</script>

Base component for disclosure:

<template>
  <ModalDisclosure
    v-bind="modal"
    class="text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
  >
    <slot />
  </ModalDisclosure>
</template>

<script>
  import { inject } from 'vue'
  import { ModalDisclosure } from 'vue-ari'

  export default {
    name: 'AppModalDisclosure',
    components: {
      ModalDisclosure,
    },
    setup() {
      const modal = inject('modalState')
      return {
        modal,
      }
    },
  }
</script>

Base component for modal:

<template>
  <ModalBackdrop
    v-bind="modal"
    class="fixed inset-0 flex items-center justify-center bg-opacity-75 bg-black"
  >
    <Modal
      v-bind="{ ...modal, ...$attrs }"
      class="max-w-xs rounded shadow-lg border border-solid border-gray-300 py-3 px-5 bg-white"
    >
      <slot />
    </Modal>
  </ModalBackdrop>
</template>

<script>
  import { inject } from 'vue'
  import { Modal, ModalBackdrop } from 'vue-ari'

  export default {
    name: 'AppModalDisclosure',
    components: {
      Modal,
      ModalBackdrop,
    },
    inheritAttrs: false,
    setup() {
      const modal = inject('modalState')
      return {
        modal,
      }
    },
  }
</script>

Inside your app:

<template>
  <AppModalProvider>
    <AppModalDisclosure>
      Open Modal
    </AppModalDisclosure>
    <AppModal>
      Modal Content
    </AppModal>
  </AppModalProvider>
</template>

<script>
  import { AppModalProvider, AppModal, AppModalDisclosure } from './components'

  export default {
    components: {
      AppModalProvider,
      AppModal,
      AppModalDisclosure,
    },
  }
</script>