Skip to content
Foundation / Mobile
Demo-First

Haptic Feedback Guide

The tactile layer of the semantic color system. Every interaction that changes system state has a corresponding haptic signal — selection, impact, and notification types mirror the agency/temporal/validation triad.

Live Trigger
Visual preview — haptics require Android Chrome / supported PWA
Select a haptic type above to preview
Design Principle

Haptic weight matches action weight. Selection = lightest (5ms pulse). Irreversible financial action = heaviest (25ms double-beat). A payment confirmation and a checkbox toggle must feel physically different — the body should know the stakes before the brain reads the screen.

Categories

Haptic Types

Selection

Subtle tick for toggle state changes, segment selection, checkbox toggle, picker scroll tick. The lightest haptic — acknowledges user choice without interrupting flow.

Web Pattern (ms)
[5]
expo-haptics
Haptics.selectionAsync()
DS Token ref
--ds-motion-interaction (80ms)
Impact — Light

Small tap feedback for minor state changes. Used at swipe-action reveal threshold, pull-to-refresh peek, and list item press.

Web Pattern (ms)
[10]
expo-haptics
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
DS Token ref
--ds-motion-fast (150ms)
Impact — Medium

Standard tap for primary actions and confirmations. Buttons, swipe-action execution, and agent task approval.

Web Pattern (ms)
[15, 10, 15]
expo-haptics
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)
DS Token ref
--ds-motion-smooth (200ms)
Impact — Heavy

Strong confirmation for irreversible or high-value actions — payment submission, contract execution, agent authorization with no-undo path.

Web Pattern (ms)
[25, 5, 25]
expo-haptics
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy)
DS Token ref
--ds-motion-deliberate (300ms)
Notification — Success

Double-tap pattern for completed operations: payment confirmed, agent task done, form submitted successfully.

Web Pattern (ms)
[10, 50, 20]
expo-haptics
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
DS Token ref
--ds-color-outcome-positive
Notification — Warning

Triple-tick for attention-required states: threshold breach, validation hold, agent uncertainty, review required.

Web Pattern (ms)
[10, 30, 10, 30, 10]
expo-haptics
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning)
DS Token ref
--ds-color-validation
Notification — Error

Error buzz for failures, invalid inputs, payment declined, agent error state, authentication failure.

Web Pattern (ms)
[30, 10, 30]
expo-haptics
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
DS Token ref
--ds-color-outcome-negative
Component Map

Which Component Triggers Which Haptic

Component → interaction → haptic category. Columns: component / what triggers it / why this weight.

Component
Interaction
Rationale
Selection[5]ms
Toggle / Switch
State change
Acknowledges binary state flip without emphasis
SegmentedControl
Segment tap
Picker-scroll equivalent — one tick per segment
Checkbox
Check / Uncheck
Lightweight confirmation of selection state
Impact — Light[10]ms
SwipeActionRow
Action reveal threshold crossed
Signals gesture has passed the 72px reveal threshold
PullToRefresh
Peek appears (32px pull)
Informs user the gesture is recognized, not yet committed
ListRow
Tap / press
Standard list-item press response
Impact — Medium[15, 10, 15]ms
Button — Primary
Tap
Primary CTA deserves medium confirmation weight
PullToRefresh
Trigger threshold (64px)
Committed gesture — data fetch begins
LongPress
500ms threshold
Context menu revealed after sustained press
ApprovalButton
Approve action
Human-in-the-loop approval tap
Impact — Heavy[25, 5, 25]ms
PaymentSubmit
Confirm payment
Irreversible financial action — heavy weight required
AgentAuthorizationGate
Authorize autonomous action
High-stakes agent authorization — maximum physical signal
SwipeDismiss
Item deleted (no undo path)
Permanent removal — heavy emphasis to prevent accidents
Notification — Success[10, 50, 20]ms
Toast — Success
Payment confirmed
Double-beat success pattern is universally recognized
AgentTaskCard
Task completed
Agent completed its assigned task successfully
FormSubmit
Form submitted successfully
Positive closure of a multi-step user flow
Notification — Warning[10, 30, 10, 30, 10]ms
ConfidenceMeter
Agent confidence drops below threshold
Requires attention but not immediate action
EscalationBanner
Review-required state
Human review needed — validation color + warning haptic
Input
Validation warning (not error)
Soft caution — field has a value but may need review
Notification — Error[30, 10, 30]ms
Input
Validation error
Failed validation — error buzz mimics device shake pattern
PaymentDeclined
Payment failed
Critical failure in financial flow
AuthFailed
Authentication failure
Security event — strong negative signal
AgentError
Agent task failed
Agent encountered unrecoverable error
Implementation

React Native — expo-haptics

import * as Haptics from 'expo-haptics';

// DS Haptic helper — mirrors the category system
export const dsHaptic = {
  // Selection — toggles, pickers, checkboxes
  selection: () => Haptics.selectionAsync(),

  // Impact — scaled by action weight
  impact: {
    light:  () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light),
    medium: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium),
    heavy:  () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy),
  },

  // Notification — outcome signals
  notification: {
    success: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success),
    warning: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning),
    error:   () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error),
  },
} as const;

// Usage in components:
function ApproveButton({ onApprove }) {
  const handlePress = async () => {
    await dsHaptic.impact.medium();          // medium = approval action
    await onApprove();
    await dsHaptic.notification.success();   // success on completion
  };
  return <Pressable onPress={handlePress}>Approve</Pressable>;
}

function PaymentConfirmButton({ onConfirm }) {
  const handlePress = async () => {
    await dsHaptic.impact.heavy();           // heavy = irreversible financial action
    await onConfirm();
    await dsHaptic.notification.success();
  };
  return <Pressable onPress={handlePress}>Confirm Payment</Pressable>;
}

Web / PWA — navigator.vibrate()

// Web haptic patterns — navigator.vibrate(pattern)
// pattern: [vibrate, pause, vibrate, ...] in ms
// Single value = one vibration of that duration

export const webHaptic = {
  selection:           () => navigator.vibrate?.(5),
  impactLight:         () => navigator.vibrate?.(10),
  impactMedium:        () => navigator.vibrate?.([15, 10, 15]),
  impactHeavy:         () => navigator.vibrate?.([25, 5, 25]),
  notificationSuccess: () => navigator.vibrate?.([10, 50, 20]),
  notificationWarning: () => navigator.vibrate?.([10, 30, 10, 30, 10]),
  notificationError:   () => navigator.vibrate?.([30, 10, 30]),
} as const;

// Feature detection — graceful degradation
function triggerHaptic(fn: () => void) {
  if (typeof navigator !== 'undefined' && 'vibrate' in navigator) fn();
  // Silently skip on unsupported platforms (desktop, iOS Safari)
}

// Note: iOS Safari does NOT support navigator.vibrate().
// Use expo-haptics for native iOS apps.
// For iOS PWAs, haptics are unavailable — rely on visual feedback alone.
Anti-Patterns

Haptic Misuse

Anti-Pattern
Problem
Fix
Haptic spam
Every interaction triggers a haptic, including hover effects, scroll events, and passive list rendering.
Only trigger haptics on state-changing interactions. Never on hover, passive scroll, or rendering. The vibration motor is not a cursor.
Wrong weight for stakes
Payment submission triggers a selection haptic (5ms) — same as checking a checkbox. User doesn't feel the gravity of the action.
Match haptic weight to action stakes. Selection = 5ms. Payment = 25ms double-beat. The physical signal must match the financial consequence.
Heavy haptic for warnings
Warning states trigger impact-heavy, causing alarm when only attention is needed.
Warnings use notification-warning ([10,30,10,30,10]). Reserve heavy impact for irreversible actions, not alerts.
Haptic without visual
Haptic fires but the UI shows no state change. User is confused about what was acknowledged.
Haptics always accompany a visible state change. Never trigger a haptic for invisible state transitions.
No reduced-motion check
Haptics fire regardless of the user's prefers-reduced-motion setting.
Check prefers-reduced-motion before triggering haptics in web contexts. If motion is reduced, skip haptics or use only notification types.