Feel the interface, not just see it.
Tactile is a macOS menu-bar utility that taps your Force Touch trackpad's haptic motor whenever the cursor passes over something clickable, in any app, system-wide. Built as an accessibility aid: instead of relying on the subtle hover-color change most apps use, you feel a physical tick under your finger when the cursor reaches a button, link, checkbox, menu, or tab.
Haptics_compressed.mp4
Tactile reads the same macOS Accessibility tree that VoiceOver uses. A global mouse monitor watches cursor movement, a background hit-tester asks the system what element is under the cursor, and when the cursor enters a clickable element, the trackpad ticks once.
The pipeline is fully event-driven: when the mouse is still, Tactile does nothing at all (0% CPU). While moving, sampling is throttled and cached per-element, so continuous use stays under a few percent of one core.
- Works everywhere: native apps, Finder (files, folders, sidebar, and desktop icons), browsers, and Electron apps; anything that exposes an accessibility tree.
- Chrome browser bridge (optional): a companion extension reads the real
page DOM, so
<div>-style buttons that never reach the accessibility tree still tick. Seeextension/. - Haptic waveforms: every feel is a composable waveform: pick a preset (taps, double/triple tap, ramps, shake, heartbeat) or compose your own pulse by pulse, per element type.
- Contextual danger feel: window close buttons and controls labeled Delete/Remove/Reset/etc. play their own warning waveform.
- State awareness: checked checkboxes and the selected tab add a confirmation pulse; optionally feel disabled controls as a dull tap.
- Hover-out feel: a separate waveform when leaving a control, so you can feel its extent.
- Spatial feedback: optional bumps at outer screen edges and when the cursor crosses window boundaries.
- Visual aids: a colored circle that rides under the cursor (green over clickable, red over destructive) and an outline around the hovered element, for low-vision use. Colors are configurable.
- Enhanced haptics (optional): drives the trackpad actuator directly so Light/Standard/Firm become physically different strengths. Uses a private system framework, loaded at runtime with automatic fallback to standard haptics if it's ever unavailable.
- Continuous vibration: with enhanced haptics on, keep the trackpad buzzing while the cursor rests on a control, fast enough (up to 250 pulses/s) to feel like a true vibration rather than a series of taps.
- Custom sounds: an optional click for external-mouse users; use a built-in sound or import your own.
- Profiles & import/export: save named snapshots of your whole setup, switch from the menu bar, and share configurations as JSON.
- Playground: real sample controls to feel your configuration live while tuning it.
- Per-app exclusions, rate limiting, dwell delay, No Lag mode, launch at login, one-click pause: the usual knobs for taste and battery.
- Keyboard haptics: tick on shortcuts, every key, modifiers, or your own recorded key combinations, each with its own waveform.
- Automatic updates: via Sparkle.
Tactile can also be used as a low-vision aid. Beyond the haptic feedback, it adds visual reinforcement for anyone who has trouble tracking a small cursor or telling what's interactive on screen:
- Element highlighting — an outline traces the exact control under the cursor, so buttons, links, and fields are unambiguous even at a glance.
- Crosshair — a large crosshair easier to keep track of pointer position on screen.
- Hover circle — a colored circle rides under the cursor (green over clickable, red over destructive), reinforcing what kind of element you're on before you click.
- Labeled elements - a small label that gives information on the hovered element, such as it's type (link, button, toggle, etc) and where it is from (menu bar, apps, dock, etc).
Together with the haptic tick, this gives low-vision users three independent signals — feel, color, and shape — for the same event.
Line.mov
Line.and.Label.mov
Highlight.mov
Circles.mov
To enable these, go to Tactile Settings -> visual aids
- A Mac with a Force Touch trackpad (built-in on modern MacBook Pro/Air, or a Magic Trackpad). Haptics are felt while a finger rests on the trackpad, which is naturally the case while using it. External mice can use the click sound.
- macOS 14.6 or later.
- The Accessibility permission (System Settings → Privacy & Security → Accessibility). Tactile walks you through this on first launch.
-
Download
Tactile.appfrom the latest release and drag it to/Applications. -
Tactile is not yet notarized by Apple, so Gatekeeper will refuse to open it on first launch (“Tactile can’t be opened because Apple cannot check it for malicious software”). Clear the quarantine flag once, in Terminal:
xattr -dr com.apple.quarantine /Applications/Tactile.app
Then open it normally. (You only need to do this once. It's the standard step for open-source Mac apps distributed without a paid notarization subscription, the full source is here for you to inspect or build yourself.)
-
Launch Tactile, grant the Accessibility permission when prompted, and you're set. The icon lives in the menu bar.
For web pages, load the companion extension for richer coverage; see
extension/README.md. Then enable Browser
integration in Settings → Apps & Browser.
Open Tactile.xcodeproj in Xcode and run. Package dependencies (Sparkle)
resolve automatically. The app is unsandboxed, the system-wide Accessibility
API requires it, so it isn't App Store distributable; distribute with Developer
ID signing and notarization.
Automatic updates use Sparkle and are driven by
appcast.xml at the repo root (the feed URL is set in
Info.plist → SUFeedURL). One-time setup, then per-release steps:
One time: signing keys
- Run Sparkle's
generate_keys(bundled with the Sparkle package / release tools). It stores a private key in your login Keychain and prints a public key. - Paste that public key into
Info.plist→SUPublicEDKey, and commit.
Each release
- Bump
MARKETING_VERSIONandCURRENT_PROJECT_VERSIONin the project. - Archive, export with Developer ID, notarize, and zip the
.app. - Sign the zip:
sign_update Tactile-x.y.z.zip→ copies anedSignature. - Add an
<item>toappcast.xmlwith<enclosure url="…" length="…" sparkle:edSignature="…"/>pointing at the zip on the GitHub release, bumpsparkle:version/shortVersionString, and push.
Users then get the update in-app via Check for Updates… or the background check.
- No network access of its own. Tactile makes exactly one kind of outbound request, the Sparkle update check to this repository, and nothing else. It has no analytics, no telemetry, and collects nothing.
- On-device only. The Accessibility permission is used solely to identify the kind of UI element under the cursor (button, link, …), never your content, and never keystrokes.
- Local IPC only. The optional browser bridge talks to the extension over a local Unix socket in your Application Support folder, never over the network.
- Auditable. The one private API (the trackpad actuator, via
MultitouchSupport) is loaded at runtime with
dlopenand falls back safely; everything else is public API. The whole source is here.
MIT © Mason Chen