Article
web-haptics: Haptic Feedback Finally Comes to iOS Safari
lochie just published a package bringing haptic feedback to the web on iOS: web-haptics. I spent four years at Apple working mostly on UIs, so I pay close attention to design and UX. In terms of presentation, use case, and attention to detail, this package comes as close to perfect as I’ve seen.
The problem
On Android, haptics have been available through navigator.vibrate(), but iOS does not support this API. There is a workaround, though – the input element with the switch attribute:
<input type="checkbox" switch>
See this switch input in action here. On iOS, the input renders not as a regular checkbox but as a switch that can be toggled on or off:

Open the link on your iPhone, tap the switch, and you’ll feel haptic feedback. As far as I know, this is currently the only known practical workaround to trigger haptic feedback on the web in iOS.
Implementation
lochie cleverly bases his package on this single workaround and pushes it to its limits. The key lines are here, creating a switch input on the fly:
js (rendered as javascript)const hapticCheckbox = document.createElement("input");hapticCheckbox.type = "checkbox";hapticCheckbox.setAttribute("switch", "");
The package also creates an associated label and clicks it to trigger haptic feedback on the connected input:
js (rendered as javascript)this.hapticLabel.click();
(My understanding is that iOS will not trigger the haptic feedback when the input is programmatically clicked – so the ‘workaround within the workaround’ is to click the associated label instead.)
Custom haptics (!)
But web-haptics can do more than just trigger a single haptic ‘pulse’. The trigger function lets you pass in your own patterns with pulses of different duration and intensity, and even delays.
Want a simple success haptic? Trigger two pulses, as shown on the package homepage:
js (rendered as javascript)trigger([{ duration: 30 },{ delay: 60, duration: 40, intensity: 1 },])
Or simply call trigger('success') as a shortcut.
Here’s an error haptic I made based on Apple’s human interface guidelines:
trigger([{ duration: 40, intensity: 0.7 },{ delay: 40, duration: 40, intensity: 0.7 },{ delay: 40, duration: 40, intensity: 0.9 },{ delay: 40, duration: 50, intensity: 0.6 },])
lochie’s website even gives you an editor to make custom haptics. As you click and drag, both pulses and code update in real time. This experience is developer bliss:

Usage
Importing the package is easy. In Rails, for example:
# config/importmap.rbpin "web-haptics", to: "https://esm.sh/web-haptics@0.0.6"
import { WebHaptics } from 'web-haptics';// In a Stimulus controller somewherelet haptics = new WebHaptics();haptics.trigger('success'); // or 'error', 'warning', etc for built-in hapticshaptics.trigger([{ duration: 40, intensity: 0.7 },{ delay: 40, duration: 40, intensity: 0.7 },])
Pass a debug option to the constructor to hear clicks in development/on desktop:
new WebHaptics({ debug: true });
Again, this package comes as close to perfect as I’ve seen. In fact, I already use it on Veritula: as you type in a text field, an error haptic plays when you exceed the max length (#4473).
I highly recommend web-haptics by lochie.
Can you refute this article?
Nobody has criticized the article yet. One criticism nobody can answer is enough to make it no longer rationally adoptable.
More articles
Veritula for Programmers
Programmers already know the rule Veritula runs on. A pull request with unresolved review comments shouldn’t get merged. On Veritula, an idea is rationally adoptable only if it has no pending criticisms, and a criticism is pending if it…
5 Minute Creativity
TL;DR: When making a decision or working to solve a problem, spend 5 minutes (using a timer) just coming up with ideas. Managing your attention like this can supercharge your creativity. A few days ago I was helping a friend flesh out an…
In Defense of Ambition
Until about a week ago, when I debated this with my friend Tom Hyde, it seemed incredibly obvious to me that wanting to be rich is rational but I could not explain why. Fundamentally I agree with Popper and Deutsch that all life is problem…


