Skip to content

Investigate iOS 26.5+ direct-tap switch fallback instead of programmatic .click() #41

@K8Cill

Description

@K8Cill

Problem

web-haptics@0.0.6 no longer produces haptics on iOS 26.5+ for the current imperative trigger path.

There are already a few reports that iOS 26.5 stopped working, but I think the important implementation detail is this: the current fallback still depends on programmatically clicking a hidden <input type="checkbox" switch>/label. That appears to be the part Apple patched.

Why this matters

The library currently exposes a clean API like:

<button onClick={() => trigger("success")}>Tap me</button>

That still makes sense for Android via navigator.vibrate(), but on iOS 26.5+ the haptic seems to require the user to directly tap the native WebKit switch control itself. Calling .click() from JavaScript is no longer enough.

Related finding

m1ckc3s/project-fathom demonstrates a small working pattern on a physical iPhone running iOS 26.5:

  • render a real <input type="checkbox" switch>
  • keep native appearance intact
  • make it transparent with opacity: 0, not display: none
  • overlay it inside the visible button bounds
  • clip the hit area to the visible button shape
  • let the user’s actual tap toggle the native control
    That is different from the current web-haptics fallback, which creates a hidden switch/label and triggers it through script.

Suggested direction

Consider adding an explicit iOS 26.5+ direct-tap integration instead of trying to keep the same imperative fallback working everywhere.

Possible API shape:

<HapticTarget type="success">
  <button>Save</button>
</HapticTarget>

or:

const { getHapticTargetProps } = useWebHaptics();

<button {...getHapticTargetProps("success")}>
  Save
</button>

The important part is that the iOS path may need to render/overlay a real native switch inside the user’s actual tap target, while Android can continue using navigator.vibrate().

Acceptance criteria

  • iOS 26.5+ has a documented path that does not rely on programmatic .click().
  • The switch remains rendered/tappable on iOS, not display: none.
  • Native switch appearance is not stripped in a way that disables the haptic.
  • The overlay is clipped to the visible target so rounded buttons do not have invisible rectangular tap areas.
  • Existing imperative trigger() behavior remains available for Android and existing integrations.
  • Docs clearly explain that custom multi-pattern haptics are not possible on iOS 26.5+ through this direct-tap path; it is limited to the native system tick.
  • Accessibility is handled deliberately, since an invisible checkbox/switch over a button can create confusing semantics if implemented naively.

Notes

This probably should not be treated as a simple bug fix to trigger(). If iOS now requires a real direct interaction with the native switch, the correct fix may need a new opt-in component/hook API for tap targets.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions