Shipkit
DocsFeaturesPricing
DocsFeaturesPricing

Command Palette

Search for a command to run...

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.