External Hooks
useWebHaptics The API 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.
๐ณ 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
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
Open in
pnpm npm yarn bun
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
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 >
);
}
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 >
);
};
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 >
);
};
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 >
);
};
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 >
);
};
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 >
);
};
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 >
);
};
const { trigger , cancel , isSupported } = useWebHaptics (options ? );
Property Description Type Default debug Enable debug mode with audio simulation for desktop testing booleanfalseshowSwitch Show a fixed-position toggle switch for haptic feedback booleanfalse
Property Description Type trigger Trigger haptic feedback with optional input and options (input?: HapticInput, options?: TriggerOptions) => Promise<void> | undefinedcancel Cancel any ongoing vibration () => voidisSupported Whether the Vibration API is supported in the current browser boolean
The trigger function accepts flexible input types:
Type Description Example numberSingle vibration duration in milliseconds trigger(100)stringBuilt-in preset name trigger('success')number[]Alternating on/off durations (ms) trigger([100, 50, 100])Vibration[]Array of vibration objects with duration, intensity, and delay trigger([{ duration: 50, intensity: 0.8 }])HapticPresetObject with a pattern property containing Vibration[] trigger({ pattern: [{ duration: 30, intensity: 1 }] })
Property Description Type Default intensity Default intensity for vibrations that don't specify their own (0โ1) number0.5
Preset Category Description successNotification Ascending double-tap indicating success warningNotification Two taps with hesitation indicating a warning errorNotification Three rapid harsh taps indicating an error lightImpact Single light tap for minor interactions mediumImpact Moderate tap for standard interactions heavyImpact Strong tap for significant interactions softImpact Soft, cushioned tap with a rounded feel rigidImpact Hard, crisp tap with a precise feel selectionSelection Subtle tap for selection changes nudgeCustom Two quick taps with a pause, for nudge or reminder buzzCustom Rapid, low-intensity sustained buzzing effect
Vibration API : On supported devices, navigator.vibrate() is called with a computed pattern
PWM Modulation : Intensity values (0โ1) are translated to pulse-width modulation patterns, alternating short on/off cycles to simulate variable intensity
Fallback : On unsupported devices (desktop), a DOM-based checkbox toggle and optional audio simulation provide visual/audio feedback for testing
Lifecycle : The WebHaptics instance is created on mount and destroyed on unmount, cleaning up DOM elements and audio context
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
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 ;