Skip to content

[Drawer] Swipe dismiss handler blocks onClick on non-interactive elements inside drawer on touch devices #4294

@doruksahin

Description

@doruksahin

Summary

The Drawer's swipe dismiss handler (useSwipeDismiss) prevents onClick from firing on non-interactive elements (e.g., <div onClick>) inside the drawer on touch devices, while native interactive elements like <button> work correctly.

Steps to reproduce

  1. Render a left-side Drawer with clickable <div onClick> elements and <button onClick> elements inside the popup content
  2. Open the drawer on a touch device (or mobile emulator)
  3. Tap a <div onClick> element → onClick does not fire
  4. Tap a <button onClick> element → onClick fires correctly

Root cause

In useSwipeDismiss.ts, startSwipeAtPosition checks if the touch target matches DEFAULT_IGNORE_SELECTOR:

const DEFAULT_IGNORE_SELECTOR = 'button,a,input,select,textarea,label,[role="button"]';
  • <button> matches → swipe tracking is skipped → normal click fires
  • <div> does NOT match → swipe tracking starts (setSwiping(true)) → touch events are consumed → onClick never synthesizes

Additional issue: data-swipe-ignore doesn't work on touch

The DrawerViewport has a data-swipe-ignore check in onPointerDown, but the handler exits early for event.pointerType === 'touch' (line ~682) before reaching that check. So the escape hatch exists but doesn't work for the input type where the bug occurs.

Prior art: Vaul

Vaul (which Base UI's drawer is inspired by) provides data-vaul-no-drag — a data attribute that opts elements out of swipe tracking for all input types including touch.

Suggested fix

One or more of:

  1. Make data-swipe-ignore work for touch events — move the check before the touch early-return in the onPointerDown handler, or add it to the onTouchStart handler
  2. Expose ignoreSelector as a configurable prop on Drawer.Root or Drawer.Viewport so users can extend DEFAULT_IGNORE_SELECTOR
  3. Add a universal data-no-swipe attribute (like Vaul's data-vaul-no-drag) that works for both pointer and touch events

Current workaround

Adding role="button" to clickable <div> elements makes them match [role="button"] in the ignore selector. This works and improves accessibility, but it's a workaround — users shouldn't need to know about internal selectors to make click handlers work inside a drawer.

Environment

  • @base-ui/react version: 1.2.0
  • Browser: Mobile Safari / Chrome on iOS/Android
  • Framework: React 19

Metadata

Metadata

Assignees

No one assigned

    Labels

    component: drawerChanges related to the drawer component.type: bugIt doesn't behave as expected.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions