Shipkit
DocsFeaturesPricing
DocsFeaturesPricing

Shipkit DocumentationBuild memory optimizationDevelopment GuideEnvironment VariablesError HandlingFile StructureMiddlewareRebranding Guide

Vercel Deployment Checks

Development GuideCLIDependency UpdatesDevToolsFeature FlagsAI PromptsSetup RequirementsSupply chaintRPCWeb Workers

FeaturesAIAnalytics IntegrationAuthenticationCMSDatabaseEmailHapticsPaymentsComponent RegistryStorageUIVisual BuilderWaitlist Feature

Quick StartDeploymentEnvironment VariablesFile StructureSetup Wizard

Caching & Rate LimitingContent ManagementError HandlingMarkdown and MDXMiddlewareProxy-Safe Server ActionsRebranding GuideWebhook Security Implementation GuideMulti-Zone ArchitectureVercel Deployment

IntegrationsPayload CMSDataFast Analytics IntegrationGoogle Analytics IntegrationGoogle Tag Manager IntegrationPostHogStatSig IntegrationUmami Analytics IntegrationAuth.js IntegrationBetter AuthClerkStack Auth IntegrationSupabase Authentication IntegrationBuilder.io IntegrationPayload CMS IntegrationAWS S3 IntegrationResend Email IntegrationUpstash Redis IntegrationVercel Blob IntegrationLemon SqueezyPolarStripe

API RoutesComponentsAPI Route SnippetsAuthentication SnippetsComponent SnippetsForm Handling SnippetsSnippets Introduction

    Web Haptics

    ShipKit integrates the web-haptics library to provide native-feeling vibration feedback on interactive UI components. Works on Android (via navigator.vibrate()) and iOS Safari (via the <input type="checkbox" switch> trick — no private APIs, no permissions).

    How It Works

    A HapticsProvider mounts at the root of the app (inside KitProvider) and registers a singleton haptic trigger. Components fire haptic patterns via either:

    1. data-haptic attribute — event delegation on pointerdown (preferred; no client component needed)
    2. Imperative haptic() function — called directly in event handlers

    Both approaches are SSR-safe and no-op silently on unsupported devices.

    Quick Start

    Haptics are enabled by default on all ShipKit interactive components. No setup required.

    To disable globally, set the environment variable:

    NEXT_PUBLIC_FEATURE_HAPTICS_ENABLED=false
    

    Components with Haptics

    ComponentPatternTriggerMethod
    Buttonlightclickdata-haptic
    Button (destructive)heavyclickdata-haptic
    Switchmediumtogglehaptic()
    Checkboxselectioncheck/uncheckhaptic()
    Togglemediumpresshaptic()
    Tabsselectiontab switchhaptic()
    Accordionselectionexpand/collapsehaptic()
    Dialogsoft open / light closeopen/closehaptic()
    Sheetsoft open / light closeopen/closehaptic()
    Drawersoft open / light closeopen/closehaptic()
    AlertDialog Actionheavyconfirmhaptic()
    AlertDialog Cancellightcancelhaptic()
    Selectselectionvalue changehaptic()
    Sliderlightvalue commithaptic()
    Dropdown Menu Itemselectionselect itemhaptic()
    Copy Buttonsuccesscopy actionhaptic()
    Share (copy link)successcopy actionhaptic()
    Toastsuccess / error (destructive)showhaptic()

    Available Patterns

    All patterns map to web-haptics presets, which match iOS UIFeedbackGenerator categories:

    PatternFeelUse Case
    lightLight tapGeneral button presses
    mediumMedium pulseSwitches, toggles
    heavyHeavy thudDestructive/confirm actions
    successDouble-pulseCopy, save, success states
    warningWarning burstCaution feedback
    errorError buzzError states
    selectionSelection tickTabs, radio, checkbox, menus
    softCushioned tapOpening overlays
    rigidCrisp tapHard UI feedback
    nudgeReminder nudgeSubtle attention
    buzzLong buzzExtended feedback

    Usage

    Using the data-haptic Attribute (Recommended)

    Any element with a data-haptic attribute will automatically fire the specified pattern on pointerdown. No imports needed, works with server components.

    <button data-haptic="light">Tap me</button>
    <div data-haptic="selection">Select me</div>
    

    The Button component does this by default:

    import { Button } from "@/components/ui/button";
    
    // Fires "light" haptic by default
    <Button>Save</Button>
    
    // Custom pattern
    <Button hapticPattern="success">Submit</Button>
    
    // Disable haptics for this button
    <Button noHaptics>No vibration</Button>
    

    Using the haptic() Function (Imperative)

    For haptics on events other than pointerdown, or from within event handlers:

    import { haptic } from "@/hooks/use-haptics";
    
    function MyComponent() {
      const handleSuccess = () => {
        haptic("success");
        // ... do stuff
      };
    
      return <div onClick={handleSuccess}>Done</div>;
    }
    

    Using the useHaptics() Hook

    For full access to all patterns and the isSupported flag:

    "use client";
    
    import { useHaptics } from "@/hooks/use-haptics";
    
    function MyComponent() {
      const { tap, toggle, success, error, isSupported, trigger, cancel } = useHaptics();
    
      return (
        <div>
          <button onClick={tap}>Tap</button>
          <button onClick={success}>Success!</button>
          <button onClick={cancel}>Stop vibrating</button>
          {!isSupported && <p>Haptics not available on this device</p>}
        </div>
      );
    }
    

    Architecture

    KitProvider
      └─ HapticsProvider              ← mounts singleton + event delegation
           └─ useHaptics()            ← registers web-haptics trigger
                └─ singletonTrigger   ← used by haptic() everywhere
    
    Components use either:
      data-haptic="pattern"           ← caught by HapticsProvider pointerdown listener
      haptic("pattern")               ← imperative, calls singleton directly
    

    Key Files

    FilePurpose
    src/hooks/use-haptics.tsHook, imperative haptic() function, types
    src/components/providers/haptics-provider.tsxSingleton mount + data-haptic event delegation
    src/components/providers/kit-provider.tsxWraps app with HapticsProvider

    Dependencies

    • web-haptics — core library
    • web-haptics/react — React hook binding (useWebHaptics)

    Browser Support

    PlatformMethodStatus
    Android Chromenavigator.vibrate()✅ Full support
    iOS Safari 17.5+<input type="checkbox" switch> trick✅ Works (no permissions)
    Desktop browsersAudio debug mode🔊 Audible feedback only
    Unsupported—Silent no-op

    Button API

    The Button component exposes two haptics-related props:

    PropTypeDefaultDescription
    noHapticsbooleanfalseDisable haptic feedback on this button
    hapticPatternHapticPattern"light"Which haptic pattern to fire on click

    Adding Haptics to New Components

    1. Option A: data-haptic attribute — add data-haptic="pattern" to the interactive element. Done.
    2. Option B: imperative — import haptic from @/hooks/use-haptics and call it in the event handler:
    import { haptic } from "@/hooks/use-haptics";
    
    // In your event handler:
    haptic("selection");
    

    No need to wrap in useEffect or call useHaptics() — the singleton is available globally once HapticsProvider mounts.