@headlessui/vue Dialog gets stuck unscrollable on iOS after touching outside it (same bug as #3234, fixed for React in #3801 but not ported)

Open Beginner friendly
#3,888 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
typescript
Domain
frontend

Research direction

Start in packages/@headlessui-vue/src/hooks/document-overflow/handle-ios-locking.ts and compare its touchstart handler with the current @headlessui/react version referenced in #3801. Verify the style state is reset between touches, then reproduce on iOS Safari with a Dialog by touching outside and scrolling or pinch-zooming inside it. Done means Dialog interaction continues working after an outside touch.

Written by the indexing model from the issue text.

Description

What package within Headless UI are you using?

@headlessui/vue

What version of that package are you using?

1.7.23 (latest)

What browser are you using?

Safari on iOS

Describe your issue

This looks like the same bug reported for @headlessui/react in #3234 and fixed there in #3801, but the fix never made it into @headlessui/vue.

In packages/@headlessui-vue/src/hooks/document-overflow/handle-ios-locking.ts the touchstart handler on doc decides on every touch whether it started inside an allowed (Dialog) container or not:

d.addEventListener(doc, 'touchstart', (e) => {
  if (e.target instanceof HTMLElement) {
    if (inAllowedContainer(e.target as HTMLElement)) {
      let rootContainer = e.target
      while (rootContainer.parentElement && inAllowedContainer(rootContainer.parentElement)) {
        rootContainer = rootContainer.parentElement!
      }
      d.style(rootContainer, 'overscrollBehavior', 'contain')
    } else {
      d.style(e.target, 'touchAction', 'none')
    }
  }
})

Once you touch outside the Dialog, touch-action: none gets applied to whatever element was touched and never resets. Because the listener stays on document, later touchstart events keep firing, but nothing clears the previously applied touchAction/overscrollBehavior styles before evaluating the new touch, so scrolling and pinch-zooming inside the Dialog stop working entirely until it's closed and reopened.

I compared this against the current @headlessui/react source for the same file and the fix in #3801 wraps the style side effects in a disposable group and disposes the previous group at the start of every touchstart, so the state gets reset and recomputed each time instead of accumulating. The Vue file I read still has the pre-fix version verbatim, no dispose/reset logic anywhere in that handler.

Reproduction

  1. Open a Dialog on iOS Safari.
  2. Scroll and pinch-zoom inside the Dialog, works fine.
  3. Tap or scroll once on the page outside the Dialog.
  4. Try to scroll or pinch-zoom inside the Dialog again, it no longer responds until the Dialog is closed and reopened.

Same repro as #3234, just against @headlessui/vue instead of @headlessui/react.

Expected behavior

Interacting with the Dialog should keep working normally on iOS even after a touch outside it happens, matching the fix already shipped for @headlessui/react in #3801.

Dominant language
TypeScript
Stars
28.7k
Forks
1.2k
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from tailwindlabs/headlessui

All issues in tailwindlabs/headlessui

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.