Fiber UI LogoFiberUI

useVibration

A specialized device hook for triggering device vibrations, safely handling unsupported environments.

Installation

npx shadcn@latest add https://r.fiberui.com/r/hooks/use-vibration.json

A React hook that provides access to the Vibration API, allowing you to trigger haptic feedback loops. Perfect for mobile games, notifications, or interactive UI elements.

Browser Support

Checks for isSupported. The Vibration API is primarily supported on mobile devices and may not have any effect on desktop browsers or devices without vibration hardware.

Features

  • Trigger Vibration - Create simple or complex vibration patterns
  • Type Safe - Fully typed for TypeScript
  • SSR Safe - Works safely with server-side rendering

Basic Usage

Basic Example

Basic Vibration

Trigger a simple 200ms haptic feedback.

Vibration API is not supported in this browser/device.

"use client";

import { useVibration } from "@repo/hooks/device/use-vibration";
import { Button } from "@repo/ui/components/button";
import { Vibrate } from "lucide-react";

export function Example1() {
    const { vibrate, isSupported } = useVibration();

    return (
        <div className="flex flex-col items-center justify-center gap-4">
            <div className="text-center">
                <h3 className="text-lg font-medium">Basic Vibration</h3>
                <p className="text-muted-foreground text-sm">
                    Trigger a simple 200ms haptic feedback.
                </p>
            </div>

            <Button
                onClick={() => vibrate(200)}
                isDisabled={!isSupported}
                className="gap-2"
            >
                <Vibrate className="h-4 w-4" />
                Vibrate Device
            </Button>

            {!isSupported && (
                <p className="text-destructive mt-2 text-sm">
                    Vibration API is not supported in this browser/device.
                </p>
            )}
        </div>
    );
}

Vibration Patterns

Vibration Patterns

Pass an array of numbers to define complex vibration sequences.

"use client";

import { useVibration } from "@repo/hooks/device/use-vibration";
import { Button } from "@repo/ui/components/button";
import { AlertTriangle, CheckCircle, Info } from "lucide-react";

export function Example2() {
    const { vibrate, isSupported } = useVibration();

    // Vibrate patterns defined in milliseconds: [vibrate, pause, vibrate, pause, ...]
    const patterns = {
        success: [100, 50, 100], // Short double tap
        error: [500, 100, 500], // Two long vibrations
        sos: [
            100, 100, 100, 100, 100, 100, 300, 100, 300, 100, 300, 100, 100,
            100, 100,
        ], // ... --- ...
    };

    return (
        <div className="flex flex-col items-center justify-center gap-6">
            <div className="text-center">
                <h3 className="text-lg font-medium">Vibration Patterns</h3>
                <p className="text-muted-foreground max-w-sm text-sm">
                    Pass an array of numbers to define complex vibration
                    sequences.
                </p>
            </div>

            <div className="flex flex-wrap items-center justify-center gap-4">
                <Button
                    variant="outline"
                    onClick={() => vibrate(patterns.success)}
                    isDisabled={!isSupported}
                    className="gap-2 border-green-500/20 text-green-600 hover:bg-green-500/10 dark:text-green-400 dark:hover:bg-green-500/20"
                >
                    <CheckCircle className="h-4 w-4" />
                    Success Match
                </Button>

                <Button
                    variant="outline"
                    onClick={() => vibrate(patterns.error)}
                    isDisabled={!isSupported}
                    className="gap-2 border-red-500/20 text-red-600 hover:bg-red-500/10 dark:text-red-400 dark:hover:bg-red-500/20"
                >
                    <AlertTriangle className="h-4 w-4" />
                    Error Alert
                </Button>

                <Button
                    variant="outline"
                    onClick={() => vibrate(patterns.sos)}
                    isDisabled={!isSupported}
                    className="gap-2"
                >
                    <Info className="h-4 w-4" />
                    SOS Signal
                </Button>
            </div>

            <Button
                variant="ghost"
                onClick={() => vibrate(0)} // Pass 0 to stop
                isDisabled={!isSupported}
                className="text-muted-foreground mt-2 text-sm"
            >
                Stop Current Vibration
            </Button>
        </div>
    );
}

Game Haptics

Game Haptics

Combine actions with rapid haptic feedback.

Score: 0

Requires a mobile device with vibration support.

"use client";

import { useState, useRef } from "react";
import { useVibration } from "@repo/hooks/device/use-vibration";
import { Button } from "@repo/ui/components/button";
import { Crosshair, Target } from "lucide-react";

export function Example3() {
    const { vibrate, isSupported } = useVibration();
    const [score, setScore] = useState(0);
    const targetRef = useRef<HTMLDivElement>(null);

    const handleShoot = () => {
        // Simple haptic feedback on every click/shot
        vibrate(50);

        // Randomly hit or miss
        const hit = Math.random() > 0.5;
        if (hit) {
            setScore((s) => s + 10);
            // Strong feedback for a hit!
            vibrate([100, 50, 200]);

            // Visual feedback
            if (targetRef.current) {
                targetRef.current.animate(
                    [
                        { transform: "scale(1)", color: "inherit" },
                        { transform: "scale(1.2)", color: "green" },
                        { transform: "scale(1)", color: "inherit" },
                    ],
                    { duration: 300 },
                );
            }
        }
    };

    return (
        <div className="flex flex-col items-center justify-center gap-6">
            <div className="text-center">
                <h3 className="text-lg font-medium">Game Haptics</h3>
                <p className="text-muted-foreground max-w-sm text-sm">
                    Combine actions with rapid haptic feedback.
                </p>
            </div>

            <div className="flex items-center gap-4">
                <span className="text-2xl font-bold tracking-tight">
                    Score: {score}
                </span>
                <Button variant="outline" size="sm" onClick={() => setScore(0)}>
                    Reset
                </Button>
            </div>

            <div
                ref={targetRef}
                className="border-primary/50 relative flex items-center justify-center rounded-full border-2 border-dashed p-12"
            >
                <Target className="absolute h-16 w-16 opacity-20" />

                <Button
                    size="lg"
                    className="relative z-10 h-24 w-24 rounded-full"
                    onClick={handleShoot}
                    isDisabled={!isSupported}
                >
                    <Crosshair className="h-8 w-8" />
                </Button>
            </div>

            {!isSupported && (
                <p className="text-destructive text-sm">
                    Requires a mobile device with vibration support.
                </p>
            )}
        </div>
    );
}

Form Feedback

Form Feedback

Submit an invalid PIN to trigger error vibration.

"use client";

import { useState } from "react";
import { useVibration } from "@repo/hooks/device/use-vibration";
import { Button } from "@repo/ui/components/button";
import { Input } from "@repo/ui/components/input";
import { Label } from "@repo/ui/components/label";
import { Card } from "@repo/ui/components/card";

export function Example4() {
    const { vibrate } = useVibration();
    const [pin, setPin] = useState("");
    const [error, setError] = useState(false);

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();

        if (pin.length !== 4) {
            setError(true);
            // Quick error buzz to alert user
            vibrate([50, 50, 50, 50]);

            // Clear error state after a moment
            setTimeout(() => setError(false), 2000);
            return;
        }

        // Success hum
        setError(false);
        vibrate(200);
        setPin("");
        alert("PIN Accepted!");
    };

    const handleInput = (val: string) => {
        // Light tap when typing
        vibrate(10);
        setPin(val);
    };

    return (
        <Card className="mx-auto w-full max-w-sm p-6">
            <form onSubmit={handleSubmit} className="flex flex-col gap-6">
                <div className="text-center">
                    <h3 className="text-lg font-medium">Form Feedback</h3>
                    <p className="text-muted-foreground text-sm">
                        Submit an invalid PIN to trigger error vibration.
                    </p>
                </div>

                <div className="flex flex-col gap-3">
                    <Label htmlFor="pin">Enter 4-digit PIN</Label>
                    <Input
                        id="pin"
                        type="password"
                        inputMode="numeric"
                        maxLength={4}
                        placeholder="••••"
                        value={pin}
                        onChange={(e) => handleInput(e.target.value)}
                        className={`text-center text-xl tracking-widest transition-all ${
                            error
                                ? "animate-in slide-in-from-left-2 border-red-500 ring-1 ring-red-500"
                                : ""
                        }`}
                    />
                    {error && (
                        <p className="text-destructive text-xs">
                            PIN must be exactly 4 digits
                        </p>
                    )}
                </div>

                <Button type="submit" className="w-full">
                    Verify
                </Button>
            </form>
        </Card>
    );
}

API Reference

Hook Signature

function useVibration(): UseVibrationReturn;

Return Value

PropertyTypeDescription
vibrate(pattern?: number | number[]) => voidTriggers a vibration pattern
isSupportedbooleantrue if Vibration API is supported

Hook Source Code

import { useCallback } from "react";

/**
 * Provides a method to trigger device vibration.
 * Safe to use in environments where vibration is not supported.
 */
export function useVibration() {
    const vibrate = useCallback((pattern: number | number[] = 200) => {
        if (
            typeof window !== "undefined" &&
            typeof navigator !== "undefined" &&
            "vibrate" in navigator
        ) {
            // Return value is boolean in supported browsers
            try {
                navigator.vibrate(pattern);
            } catch (e) {
                console.warn("Vibration API failed", e);
            }
        }
    }, []);

    const isSupported =
        typeof window !== "undefined" &&
        typeof navigator !== "undefined" &&
        "vibrate" in navigator;

    return { vibrate, isSupported };
}