npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@j1nn0/vue-modal-dialog

v0.9.0

Published

A reusable Vue 3 modal dialog component with focus trap and ARIA support

Readme

vue-modal-dialog

A reusable Vue 3 modal dialog component with focus trap and ARIA accessibility support.

📦 Project Info

License npm version Downloads

⚙️ Build & Quality

Bundle Size Vite

🛠 Tech Stack

Vue JavaScript ESLint Prettier Vitest


📑 Table of Contents


✨ Features

  • Vue 3 support
  • Focus trap inside the modal
  • Keyboard accessibility (Escape to close)
  • Backdrop with blur and fade animation
  • Header, body, and footer slots
  • Optional footer slot
  • Close button in the header
  • Configurable dialog size: sm, md, lg, fullscreen
  • Configurable dialog width (supports custom widths via width prop for flexible layouts)
  • Supports dark mode and light mode via the mode prop ("light", "dark", or null to follow OS/browser preference)

💾 Installation

npm install @j1nn0/vue-modal-dialog

or

yarn add @j1nn0/vue-modal-dialog

⚙️ Peer Dependencies

Before using this component, make sure you have installed the following peer dependencies:

npm install vue @vueuse/core @vueuse/integrations focus-trap

or

yarn add vue @vueuse/core @vueuse/integrations focus-trap

These dependencies are required for the library to function properly.


🛠 Usage

You can use this component in two ways:

  1. Import individually (recommended, enables tree-shaking)
  2. Register globally as a Vue plugin

1️⃣ Individual Import (recommended)

<script setup>
import { ref } from 'vue';
import { VueModalDialog } from '@j1nn0/vue-modal-dialog';
import '@j1nn0/vue-modal-dialog/dist/vue-modal-dialog.css';

const isOpen = ref(false);

const submitForm = () => {
  alert('Form submitted!');
  isOpen.value = false;
};
</script>

<template>
  <button @click="isOpen = true">Open Dialog</button>

  <VueModalDialog v-model="isOpen">
    <!-- Header slot -->
    <template #header> Dialog Title </template>

    <!-- Body slot (default) -->
    <p>
      This is the body content of the dialog. It supports long text and will wrap automatically.
    </p>

    <!-- Footer slot (optional) -->
    <template #footer>
      <button @click="isOpen = false">Cancel</button>
      <button @click="submitForm">Submit</button>
    </template>
  </VueModalDialog>
</template>

2️⃣ Global Plugin Registration

// main.js
import { createApp } from 'vue';
import App from './App.vue';

import { VueModalDialogPlugin } from '@j1nn0/vue-modal-dialog';
import '@j1nn0/vue-modal-dialog/dist/vue-modal-dialog.css';

const app = createApp(App);

// Registers globally as <VueModalDialog> by default
app.use(VueModalDialogPlugin);
// Or specify a custom name
// app.use(VueModalDialogPlugin, { name: 'CustomName' });

app.mount('#app');

Use <VueModalDialog> (or your custom name) anywhere in your app without importing it:

<template>
  <VueModalDialog v-model="isOpen">
    <template #header> Global Dialog </template>
    <p>Body content</p>
  </VueModalDialog>
</template>

🌐 CDN Usage

You can use @j1nn0/vue-modal-dialog via CDN without any bundler. Both individual import and global plugin usage are supported.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue Modal Dialog CDN Example</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://unpkg.com/tabbable/dist/index.umd.js"></script>
    <script src="https://unpkg.com/focus-trap/dist/focus-trap.umd.js"></script>
    <script src="https://unpkg.com/@vueuse/shared"></script>
    <script src="https://unpkg.com/@vueuse/core"></script>
    <script src="https://unpkg.com/@vueuse/integrations"></script>
    <link
      rel="stylesheet"
      href="https://unpkg.com/@j1nn0/vue-modal-dialog/dist/vue-modal-dialog.css"
    />
    <script src="https://unpkg.com/@j1nn0/vue-modal-dialog/dist/vue-modal-dialog.umd.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- Individual Import -->
      <button type="button" @click="isOpenImport = true">Open Import Dialog</button>
      <vue-modal-dialog v-model="isOpenImport">
        <template #header>Import Dialog Title</template>
        <p>Body content goes here</p>
        <template #footer>
          <button @click="isOpenImport = false">Close</button>
        </template>
      </vue-modal-dialog>

      <!-- Global Plugin -->
      <button type="button" @click="isOpenGlobal = true">Open Global Dialog</button>
      <global-plugin-modal-dialog v-model="isOpenGlobal">
        <template #header>Global Dialog Title</template>
        <p>Body content goes here</p>
        <template #footer>
          <button @click="isOpenGlobal = false">Close</button>
        </template>
      </global-plugin-modal-dialog>
    </div>

    <script>
      const { createApp, ref } = Vue;
      const { VueModalDialogPlugin, VueModalDialog } = J1nn0VueModalDialog;

      const app = createApp({
        setup() {
          const isOpenImport = ref(false);
          const isOpenGlobal = ref(false);

          return { isOpenImport, isOpenGlobal };
        },
      });

      // Individual import registration
      app.component('VueModalDialog', VueModalDialog);

      // Global plugin registration (default name: 'VueModalDialog')
      app.use(VueModalDialogPlugin, { name: 'GlobalPluginModalDialog' });

      app.mount('#app');
    </script>
  </body>
</html>

📌 Props

| Prop | Type | Default | Description | | ---------- | --------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------ | | backdrop | Boolean | String | true | true = click on backdrop closes dialog, "static" = backdrop does nothing | | escape | Boolean | true | Pressing Escape key closes the dialog | | position | String | "center" | Position of the dialog: "center" or "top" | | width | String | "md" | Dialog width. Presets: sm, md, lg, fullscreen. Also supports custom CSS width, e.g. "400px", "50%", "80vw" | | mode | String | null | null | Dialog color mode: "light" for light mode, "dark" for dark mode, null to follow the OS/browser preference |


🎛 Slots

| Slot | Description | | -------- | --------------------------------------------------------- | | header | Optional. Content for the header. × button always present | | default | Content for the body of the dialog | | footer | Optional. Content for footer, not rendered if empty |


♿ Accessibility

  • role="dialog" + aria-modal="true"
  • aria-labelledby points to header slot
  • aria-describedby points to body slot
  • Close button has aria-label="Close"
  • Focus trap inside the dialog ensures keyboard navigation
  • Escape key closes the dialog if enabled

🎨 Styles

  • Dialog width: sm, md, lg, fullscreen
  • Dialog height: auto, max 80vh (default), scrollable if content overflows
  • Word wrapping enabled in header and body
  • Backdrop has fade-in/out animation with blur effect
  • Supports Light and Dark mode via mode prop

CSS Custom Properties

:root {
  /* Backdrop */
  --j1nn0-vue-modal-dialog-backdrop-z-index: 1000;
  --j1nn0-vue-modal-dialog-backdrop-background: rgba(0, 0, 0, 0.6);
  --j1nn0-vue-modal-dialog-backdrop-blur: 2px;

  /* Dialog */
  --j1nn0-vue-modal-dialog-border: none;
  --j1nn0-vue-modal-dialog-border-radius: 8px;
  --j1nn0-vue-modal-dialog-width: 90%;
  --j1nn0-vue-modal-dialog-max-width-sm: 300px;
  --j1nn0-vue-modal-dialog-max-width-md: 600px;
  --j1nn0-vue-modal-dialog-max-width-lg: 900px;
  --j1nn0-vue-modal-dialog-max-height: 80vh;
  --j1nn0-vue-modal-dialog-text-color: #000000;

  /* Header */
  --j1nn0-vue-modal-dialog-header-background: #f5f5f5;
  --j1nn0-vue-modal-dialog-header-padding: 1rem;

  /* Body */
  --j1nn0-vue-modal-dialog-body-background: #fff;
  --j1nn0-vue-modal-dialog-body-padding: 1rem;

  /* Footer */
  --j1nn0-vue-modal-dialog-footer-background: #f5f5f5;
  --j1nn0-vue-modal-dialog-footer-padding: 1rem;

  /* Dark mode */
  --j1nn0-vue-modal-dialog-backdrop-background-dark: rgba(255, 255, 255, 0.2);
  --j1nn0-vue-modal-dialog-border-dark: none;
  --j1nn0-vue-modal-dialog-header-background-dark: #1f2937;
  --j1nn0-vue-modal-dialog-footer-background-dark: #1f2937;
  --j1nn0-vue-modal-dialog-body-background-dark: #111827;
  --j1nn0-vue-modal-dialog-text-color-dark: #f9fafb;
}

📝 Notes on Multiple Modals

This library is designed with the assumption that only one modal is open at a time.
Opening multiple modals simultaneously may cause the following issues:

  • Backdrops stacking, making the screen too dark
  • Focus trap not functioning correctly
  • Escape key behavior becoming ambiguous
  • Accessibility attributes (e.g. aria-hidden) breaking

Therefore, it is recommended to control modals on the application side so that only one is visible at any given time.

At present, supporting multiple simultaneous modals is not planned.
If there is strong demand, it may be considered in the future.


🏷 License

MIT License

Copyright © 2025–PRESENT j1nn0