shadcn-ahooks
External Hooks

useWebHaptics

The API useWebHaptics

useWebHaptics

A React hook for triggering haptic feedback on the web using the Vibration API, with built-in preset patterns inspired by iOS haptic feedback styles and PWM-based intensity modulation.

Features

  • ๐Ÿ“ณ Cross-browser haptic feedback via the Vibration API
  • ๐ŸŽ›๏ธ PWM intensity modulation for fine-grained vibration control
  • ๐ŸŽจ Built-in presets mirroring iOS haptic styles (success, warning, error, light, medium, heavy, soft, rigid, selection, nudge, buzz)
  • ๐Ÿ”ข Flexible input: preset name, duration number, number[] pattern, or custom Vibration[]
  • ๐Ÿ› Debug mode with audio simulation for desktop development
  • ๐Ÿงน Automatic cleanup on unmount

When to Use

  • Button press / tap feedback in mobile web apps
  • Form validation feedback (success, error, warning)
  • Game interactions and UI transitions
  • Any scenario where tactile feedback enhances UX on supported devices

Installation

Open in
pnpm dlx shadcn@latest add https://shadcn-ahooks.vercel.app/r/useWebHaptics.json
npx shadcn@latest add https://shadcn-ahooks.vercel.app/r/useWebHaptics.json
yarn shadcn@latest add https://shadcn-ahooks.vercel.app/r/useWebHaptics.json
bun shadcn@latest add https://shadcn-ahooks.vercel.app/r/useWebHaptics.json

Usage

import useWebHaptics from "@/src/hooks/external-hooks/useWebHaptics";

function App() {
  const { trigger, cancel, isSupported } = useWebHaptics();

  return (
    <div>
      <p>Haptics supported: {isSupported ? "Yes" : "No"}</p>
      <button onClick={() => trigger("success")}>Success</button>
      <button onClick={() => trigger("error")}>Error</button>
      <button onClick={cancel}>Cancel</button>
    </div>
  );
}

Examples

Basic Presets

import React from "react";
import useWebHaptics from "@/src/hooks/external-hooks/useWebHaptics";

export default () => {
  const { trigger, isSupported } = useWebHaptics();

  const presets = [
    "success",
    "warning",
    "error",
    "light",
    "medium",
    "heavy",
    "soft",
    "rigid",
    "selection",
    "nudge",
    "buzz",
  ] as const;

  return (
    <div>
      <p>Vibration API supported: {isSupported ? "โœ… Yes" : "โŒ No"}</p>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
        {presets.map((name) => (
          <button key={name} onClick={() => trigger(name)}>
            {name}
          </button>
        ))}
      </div>
    </div>
  );
};

Custom Duration

import React from "react";
import useWebHaptics from "@/src/hooks/external-hooks/useWebHaptics";

export default () => {
  const { trigger } = useWebHaptics();

  return (
    <div>
      <button onClick={() => trigger(100)}>Vibrate 100ms</button>
      <button onClick={() => trigger(500)}>Vibrate 500ms</button>
    </div>
  );
};

Custom Pattern with Intensity

import React from "react";
import useWebHaptics from "@/src/hooks/external-hooks/useWebHaptics";

export default () => {
  const { trigger } = useWebHaptics();

  const customPattern = [
    { duration: 50, intensity: 1.0 },
    { delay: 100, duration: 30, intensity: 0.5 },
    { delay: 100, duration: 80, intensity: 0.8 },
  ];

  return (
    <div>
      <button onClick={() => trigger(customPattern)}>Custom Pattern</button>
      <button onClick={() => trigger(customPattern, { intensity: 0.3 })}>
        Custom (low intensity)
      </button>
    </div>
  );
};

Number Array Pattern

import React from "react";
import useWebHaptics from "@/src/hooks/external-hooks/useWebHaptics";

export default () => {
  const { trigger } = useWebHaptics();

  // number[] shorthand: alternating on/off durations (ms)
  return (
    <div>
      <button onClick={() => trigger([100, 50, 100])}>
        On 100ms โ†’ Off 50ms โ†’ On 100ms
      </button>
      <button onClick={() => trigger([200, 100, 200, 100, 200])}>
        Triple pulse
      </button>
    </div>
  );
};

Debug Mode with Audio Simulation

import React, { useState } from "react";
import useWebHaptics from "@/src/hooks/external-hooks/useWebHaptics";

export default () => {
  const [debug, setDebug] = useState(true);
  const { trigger } = useWebHaptics({ debug });

  return (
    <div>
      <label>
        <input
          type="checkbox"
          checked={debug}
          onChange={(e) => setDebug(e.target.checked)}
        />
        Debug mode (audio feedback)
      </label>
      <div style={{ marginTop: 12, display: "flex", gap: 8 }}>
        <button onClick={() => trigger("success")}>Success</button>
        <button onClick={() => trigger("error")}>Error</button>
        <button onClick={() => trigger("heavy")}>Heavy</button>
      </div>
    </div>
  );
};

Cancel Ongoing Vibration

import React from "react";
import useWebHaptics from "@/src/hooks/external-hooks/useWebHaptics";

export default () => {
  const { trigger, cancel } = useWebHaptics();

  return (
    <div>
      <button onClick={() => trigger("buzz")}>Start Buzz (1s)</button>
      <button onClick={cancel}>Cancel</button>
    </div>
  );
};

API

const { trigger, cancel, isSupported } = useWebHaptics(options?);

Options

PropertyDescriptionTypeDefault
debugEnable debug mode with audio simulation for desktop testingbooleanfalse
showSwitchShow a fixed-position toggle switch for haptic feedbackbooleanfalse

Result

PropertyDescriptionType
triggerTrigger haptic feedback with optional input and options(input?: HapticInput, options?: TriggerOptions) => Promise<void> | undefined
cancelCancel any ongoing vibration() => void
isSupportedWhether the Vibration API is supported in the current browserboolean

HapticInput

The trigger function accepts flexible input types:

TypeDescriptionExample
numberSingle vibration duration in millisecondstrigger(100)
stringBuilt-in preset nametrigger('success')
number[]Alternating on/off durations (ms)trigger([100, 50, 100])
Vibration[]Array of vibration objects with duration, intensity, and delaytrigger([{ duration: 50, intensity: 0.8 }])
HapticPresetObject with a pattern property containing Vibration[]trigger({ pattern: [{ duration: 30, intensity: 1 }] })

TriggerOptions

PropertyDescriptionTypeDefault
intensityDefault intensity for vibrations that don't specify their own (0โ€“1)number0.5

Built-in Presets

PresetCategoryDescription
successNotificationAscending double-tap indicating success
warningNotificationTwo taps with hesitation indicating a warning
errorNotificationThree rapid harsh taps indicating an error
lightImpactSingle light tap for minor interactions
mediumImpactModerate tap for standard interactions
heavyImpactStrong tap for significant interactions
softImpactSoft, cushioned tap with a rounded feel
rigidImpactHard, crisp tap with a precise feel
selectionSelectionSubtle tap for selection changes
nudgeCustomTwo quick taps with a pause, for nudge or reminder
buzzCustomRapid, low-intensity sustained buzzing effect

How It Works

  1. Vibration API: On supported devices, navigator.vibrate() is called with a computed pattern
  2. PWM Modulation: Intensity values (0โ€“1) are translated to pulse-width modulation patterns, alternating short on/off cycles to simulate variable intensity
  3. Fallback: On unsupported devices (desktop), a DOM-based checkbox toggle and optional audio simulation provide visual/audio feedback for testing
  4. Lifecycle: The WebHaptics instance is created on mount and destroyed on unmount, cleaning up DOM elements and audio context

Notes

  • The Vibration API is primarily supported on Android Chrome; iOS Safari does not support it
  • Duration per vibration phase is capped at 1000ms
  • Debug mode uses Web Audio API to synthesize click sounds that approximate haptic feedback
  • The showSwitch option renders a fixed-position label/checkbox in the DOM for visual feedback
  • The hook instance is stable across re-renders; only debug and showSwitch react to option changes
  • isSupported is a static value determined at module load time

TypeScript

interface Vibration {
  duration: number;
  intensity?: number;
  delay?: number;
}

type HapticPattern = number[] | Vibration[];

interface HapticPreset {
  pattern: Vibration[];
}

type HapticInput = number | string | HapticPattern | HapticPreset;

interface TriggerOptions {
  intensity?: number;
}

interface WebHapticsOptions {
  debug?: boolean;
  showSwitch?: boolean;
}

interface UseWebHapticsResult {
  trigger: (
    input?: HapticInput,
    options?: TriggerOptions
  ) => Promise<void> | undefined;
  cancel: () => void;
  isSupported: boolean;
}

function useWebHaptics(options?: WebHapticsOptions): UseWebHapticsResult;

On this page