sticky.codes

web-haptics

A zero-dependency library that delivers haptic feedback on the mobile web via the browser's Vibration API, with first-class bindings for React, Vue, and Svelte.

Overview

web-haptics wraps the Web Vibration API behind a structured, preset-driven interface. Rather than passing raw millisecond arrays to navigator.vibrate, callers use semantic names ("success", "error", "light", "selection", etc.) that map to patterns designed to follow Apple's Human Interface Guidelines for haptic feedback.

The library is aimed at developers building mobile-facing web UIs who want tactile feedback on buttons, toggles, pickers, and similar interactive elements. It silently no-ops on platforms that do not support the Vibration API (desktops, iOS Safari), so no feature detection is required in consuming code.

The package ships four entry points from a single npm install: a vanilla WebHaptics class, and framework-specific bindings for React (useWebHaptics), Vue (useWebHaptics), and Svelte (createWebHaptics). The repository is a pnpm monorepo containing the core library, a marketing/documentation site, and four example applications.

Architecture

web-haptics (repo root)
├── packages/
│   └── web-haptics/          # Published npm package (web-haptics)
│       └── src/
│           ├── lib/web-haptics/   # Core vanilla implementation
│           ├── react/             # React hook
│           ├── vue/               # Vue composable
│           └── svelte/            # Svelte factory function
├── apps/
│   ├── react-example/        # Vite + React demo
│   ├── vue-example/          # Vite + Vue demo
│   ├── svelte-example/       # Vite + Svelte demo
│   └── typescript-example/   # Vite + vanilla TS demo
└── site/                     # Marketing/docs site (React, deployed to haptics.lochie.me)

The core library (packages/web-haptics) is built with tsup into CJS and ESM outputs under dist/. Each framework adapter is a separate export path (e.g. web-haptics/react) so consumers only bundle what they use. The package.json exports map declares all four paths with types, ESM, and CJS variants.

All workspace packages reference the library via "web-haptics": "workspace:*", so local development uses the source directly.

Key modules

packages/web-haptics/src/lib/web-haptics/index.ts

The WebHaptics class — the single source of truth for all vibration logic. Exposes:

  • trigger(input?, options?) — resolves the input (preset name, number, number[], Vibration[], or HapticPreset) to a pattern, then calls navigator.vibrate with the appropriate timing sequence. Returns a Promise<void>.
  • cancel() — stops any in-progress vibration by calling navigator.vibrate(0).
  • destroy() — cleans up DOM elements and audio resources created for the optional debug mode.
  • setDebug(boolean) / setShowSwitch(boolean) — runtime configuration.
  • static isSupported — boolean check for Vibration API availability.

Constructor options: debug (plays audio tones on desktop for development testing, default false) and showSwitch (injects a toggle into the DOM, default false).

packages/web-haptics/src/lib/web-haptics/patterns.ts

Exports defaultPatterns, a record of named HapticPreset objects. These are the built-in presets:

NameDescription
successTwo quick taps
nudgeStrong tap then soft tap
errorThree sharp taps
buzzLong 1 s vibration
light, medium, heavyImpact presets matching HIG intensity tiers
soft, rigidAdditional impact variants
selectionDiscrete tick for pickers/steppers
warningCautionary notification pattern

Consumers can import defaultPatterns directly to pass a preset as a value rather than a string literal.

packages/web-haptics/src/lib/web-haptics/types.ts

Type definitions shared across the whole package:

  • Vibration{ duration: number; delay?: number; intensity?: number }
  • HapticPatternnumber | number[] | Vibration[]
  • HapticPreset{ pattern: Vibration[]; description?: string }
  • HapticInput — union of all accepted trigger() argument shapes
  • TriggerOptions{ intensity?: number } (0–1 scale, default 0.5)
  • WebHapticsOptions — constructor options (debug, showSwitch)

packages/web-haptics/src/react/useWebHaptics.ts

A React hook that instantiates WebHaptics once (via useRef or useMemo, exact implementation not included in provided files) and returns a stable object with trigger and cancel. Exported from web-haptics/react.

packages/web-haptics/src/vue/useWebHaptics.ts

A Vue 3 composable following the same contract as the React hook. Exported from web-haptics/vue. The corresponding types.ts at packages/web-haptics/src/vue/types.ts declares the composable's return type.

packages/web-haptics/src/svelte/createWebHaptics.ts

A factory function for Svelte (not a store or action) that returns { trigger, cancel, destroy }. The caller is responsible for calling destroy() in onDestroy, as shown in the README example. Exported from web-haptics/svelte.

packages/web-haptics/src/index.ts

The root entry point (web-haptics). Re-exports WebHaptics, defaultPatterns, the package version, and all public types. Framework adapters are not re-exported here — they are only available through their dedicated subpath exports.

packages/web-haptics/tsup.config.ts

Build configuration for tsup. Produces CJS (.js) and ESM (.mjs) bundles plus .d.ts declaration files for each of the four export paths. The sideEffects: false flag in package.json enables tree-shaking in bundlers.

site/

A standalone React + Vite application that serves as the public documentation site at haptics.lochie.me. It uses web-haptics via workspace:* and includes a pattern builder UI, installation instructions, usage examples, and a QR code for testing on a real mobile device. The site depends on motion (Framer Motion) for animations and torph for animated text. Analytics are injected via a Vite plugin in site/vite.config.ts.

Getting started

This repo uses pnpm workspaces. Node version is not pinned in the repo files, but the TypeScript target is ES2020/ES2022.

Install all workspace dependencies:

pnpm install:all

Watch the library and run the documentation site concurrently:

pnpm dev

This runs tsup --watch on the library and, on a successful build, starts the site Vite dev server.

Build the library only:

pnpm build

Output goes to packages/web-haptics/dist/.

Run a specific example app (e.g. the React example):

cd apps/react-example
pnpm dev

Lint the library source:

pnpm lint

Release (maintainers only):

pnpm release

This runs pnpm build, publishes via Changesets, and pushes tags.

Notable details

Silently no-ops on unsupported platforms. The library checks WebHaptics.isSupported internally. Callers do not need guards; trigger() resolves without error on desktop or iOS Safari.

Debug mode exists for desktop development. Passing { debug: true } to the WebHaptics constructor plays audio feedback in place of vibration, allowing developers to test haptic patterns without a physical mobile device.

The Svelte API differs from React and Vue. React and Vue use a hook/composable pattern with automatic lifecycle management. Svelte uses a plain factory function (createWebHaptics) and requires the caller to call destroy() manually in onDestroy. This is a deliberate choice to avoid Svelte store complexity.

The example apps alias subpath imports back to source in Vite config. Because the built dist/ may not exist during development, apps/svelte-example/vite.config.ts and apps/vue-example/vite.config.ts use resolve.alias to point web-haptics/svelte and web-haptics/vue directly at the TypeScript source under packages/web-haptics/src/. The React example does not need this because the workspace package.json exports map is sufficient for the React Vite plugin.

Changesets manages versioning. All packages use @changesets/cli. Running pnpm version calls changeset version and stages the result. Commit messages must follow Conventional Commits (commitlint.config.cjs).

Preset design follows Apple HIG. The pattern values in defaultPatterns and the trigger name taxonomy ("success", "error", "warning", "light", "medium", "heavy", "selection") are deliberately aligned with iOS UIFeedbackGenerator semantics, as documented in SKILL.md.