Skip to content

zwiesenthal/headless-ui-dialog-issue

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Headless UI Dialog - Escape Key Propagation Issue

This is a minimal reproduction demonstrating that the Headless UI Dialog component does not prevent the Escape key event from propagating to other keyboard event listeners when the dialog is closed.

The Issue

When a Dialog is open and the user presses Escape:

  1. ✅ The Dialog closes correctly (via onClose)
  2. Bug: The Escape key event also propagates to other global keyboard listeners

This is problematic in applications where other components listen for the Escape key (e.g., navigation back buttons, closing other UI elements, etc.).

Expected Behavior

When a Dialog is open and captures the Escape key to close:

  • The Escape key event should be consumed by the Dialog
  • Other keyboard listeners should NOT receive the Escape key event
  • This is standard modal behavior - modals should capture keyboard events and prevent them from affecting the underlying page

Actual Behavior

The Dialog closes, but the Escape key event continues to propagate, triggering other keyboard event listeners in the application.

Reproduction Steps

  1. Install dependencies:

    npm install
  2. Start the dev server:

    npm run dev
  3. Open the application in your browser

  4. Click "Open Drawer" to open the Dialog

  5. Press the Escape key

  6. Observe the Event Log:

    • You'll see "Dialog closed via onClose" (expected)
    • You'll also see "Global Escape listener triggered!" (bug)

Real-World Impact

In our production application, we have:

  • A Dialog/Drawer component for detail views
  • A "Back" button component that listens for Escape to navigate back
  • When a drawer is open and the user presses Escape, both the drawer closes AND the back navigation is triggered, causing unwanted navigation

Workaround

Currently, we have to add checks in every component that listens for Escape:

useEffect(() => {
  const handleEscape = event => {
    if (event.key === 'Escape') {
      // Workaround: Check if a dialog is open
      if (document.querySelector('[role="dialog"]')) {
        return; // Don't handle if dialog is open
      }
      // Handle escape...
    }
  };
  window.addEventListener('keydown', handleEscape);
  return () => window.removeEventListener('keydown', handleEscape);
}, []);

This is not ideal because:

  • It requires every Escape listener to know about dialogs
  • It's easy to forget and leads to bugs
  • It couples unrelated components
  • The Dialog should handle this internally

Suggested Fix

The Dialog component should call event.stopPropagation() and/or event.preventDefault() when handling the Escape key, preventing it from reaching other listeners.

Environment

  • @headlessui/react: 2.2.0
  • React: 18.3.1
  • Browser: All modern browsers

Related

This is similar to how other modal libraries handle keyboard events - they consume the Escape key when closing to prevent it from affecting the rest of the application.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published