Most macOS hover states are a small color change. If your eyes are tired, or your vision is low, or the button is a subtle ghost control, you can easily miss it. I wanted to feel when my cursor reached something clickable, the way a real switch has a detent. So I built Tactile, a menu bar app that taps the trackpad's haptic motor whenever the pointer is over a clickable element, anywhere on the system.
The pipeline
The whole thing is event-driven:
- A
CGEventTapwatches cursor movement. When the mouse is still, nothing runs, so it costs 0% CPU. - On movement, a throttled hit-test asks the macOS Accessibility tree what element is under the cursor. This is the same tree VoiceOver uses.
- A classifier decides whether that element is clickable (button, link, checkbox, menu item, tab, Dock item, and so on). Finder is a fun edge case: files and sidebar items expose an Open action rather than Press.
- If the cursor just entered a clickable element, the trackpad ticks once. Results are cached per element so continuous movement stays under a few percent of one core.
Why the accessibility tree
Reading the AX tree means Tactile learns only the kind of element under the pointer, never its content and never your keystrokes. It also means coverage is broad by default: native apps, Finder, browsers, and Electron all expose accessibility roles. The privacy story falls out of the design rather than being bolted on.
The haptics
The default engine is the public NSHapticFeedbackManager. There is also an optional engine that drives the trackpad actuator directly through a private framework, loaded at runtime with dlopen and a safe fallback. That unlocks real strength differences and continuous vibration by pacing pulses on a dedicated thread, fast enough to feel like a buzz rather than a series of taps.
The browser gap
Some web controls are divs with a click handler and cursor: pointer and no ARIA role, so they never reach the accessibility tree. For those, an optional Chrome extension reports element rectangles over a local Unix socket, and a shared fire-region memory dedupes the two paths so nothing double-fires.
Accessibility beyond haptics
Because it started as a low-vision aid, Tactile gives three independent signals for the same event: feel (the tick), color (a green circle over clickable, red over destructive), and shape (an outline around the control, plus a crosshair and a small labeled pill telling you the element type and where it came from).
Honest limitations
It needs a Force Touch trackpad, since that is the hardware with the haptic motor, and macOS 14.6 or later. It is not notarized yet, so there is a one-time quarantine step, and the full source is available to inspect or build. MIT licensed, and the only network call it makes is the Sparkle update check to its own repo.
Top comments (0)