VUE

useModal Composable

Reusable modal management with keyboard navigation and backdrop click handling

Vue3ComposablesModalUIAccessibility

Code

import { ref, onMounted, onUnmounted, type Ref } from 'vue';
import { useFocusTrap } from './useFocusTrap'; // Reuse focus trap composable

export function useModal(
  modalRef: Ref<HTMLElement | null>,
  backdropRef: Ref<HTMLElement | null> = ref(null)
) {
  const isOpen = ref(false);
  const { activate: activateFocusTrap, deactivate: deactivateFocusTrap } = useFocusTrap(modalRef, isOpen);

  // Open modal
  const open = () => {
    __TOKEN_36__ (isOpen.value) return;
    isOpen.value = true;
    document.body.style.overflow = 'hidden'; // Prevent body scroll
    setTimeout(() => activateFocusTrap(), 0); // Wait for modal to render
  };

  // Close modal
  const close = () => {
    __TOKEN_39__ (!isOpen.value) return;
    isOpen.value = false;
    document.body.style.overflow = ''; // Restore body scroll
    deactivateFocusTrap();
  };

  // Toggle modal
  const toggle = () => {
    isOpen.value ? close() : open();
  };

  // Handle escape key
  const handleEscapeKey = (e: KeyboardEvent) => {
    __TOKEN_43__ (e.key === 'Escape' && isOpen.value) {
      close();
    }
  };

  // Handle backdrop click
  const handleBackdropClick = (e: MouseEvent) => {
    __TOKEN_45__ (backdropRef.value && e.target === backdropRef.value && isOpen.value) {
      close();
    }
  };

  onMounted(() => {
    document.addEventListener('keydown', handleEscapeKey);
    __TOKEN_46__ (backdropRef.value) {
      backdropRef.value.addEventListener('click', handleBackdropClick);
    }
  });

  onUnmounted(() => {
    document.removeEventListener('keydown', handleEscapeKey);
    __TOKEN_47__ (backdropRef.value) {
      backdropRef.value.removeEventListener('click', handleBackdropClick);
    }
    // Cleanup
    __TOKEN_48__ (isOpen.value) {
      document.body.style.overflow = '';
    }
  });

  return {
    isOpen,
    open,
    close,
    toggle
  };
}

// Usage example
// const modalRef = ref<HTMLElement | null>(null);
// const backdropRef = ref<HTMLElement | null>(null);
// const { isOpen, open, close } = useModal(modalRef, backdropRef);
// open(); // Open modal
// close(); // Close modal