Skip to Content

Getting Started

Breathe life into your user interface. web-haptics-toast is a single-click upgrade that bridges the gap between digital pixels and physical touch. By bundling a precision vibration engine directly into a polished toast component, we ensure every interaction is felt as much as it’s seen.

Sonner-compatible out of the box. If you’re coming from Sonner, you already know the API. Transition instantly without changing your codebase—just more weight, more feeling, and a solid physical experience.

Install

pnpm i web-haptics-toast

Add Toaster to your app

Import the package styles once (typically in the same file as <Toaster /> or your root layout). You can place <Toaster /> anywhere; in the App Router it is often in layout.tsx.

import { Toaster } from 'web-haptics-toast'; import 'web-haptics-toast/dist/styles.css'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> {children} <Toaster /> </body> </html> ); }

Haptics run automatically when each toast appears (default true). To use vibration only on certain buttons or actions (not on every toast), see Haptics → manual haptics and <Toaster /> props.

Render a toast

function MyToast() { return <button onClick={() => `toast`('This is a toast')}>Render my toast</button>; }

(Optional) Enable production behaviors

web-haptics-toast keeps Sonner-compatible behavior by default. If you want “production-grade” features that can change toast semantics (like dedupe/update), enable them from one place: <Toaster system={...} />.

import { Toaster, toast } from 'web-haptics-toast'; import 'web-haptics-toast/dist/styles.css'; export default function App() { return ( <> <Toaster system={{ dedupe: { enabled: true }, dismissReason: { enabled: true }, }} /> <button onClick={() => toast.error('Payment failed', { dedupeKey: 'payment-error', onClose: (_toast, reason) => { console.log('Closed with reason:', reason); }, }) } > Trigger toast </button> </> ); }

Dedupe / update by dedupeKey (OFF by default)

When enabled in system, calling toast.*(..., { dedupeKey }) updates the existing toast with the same key instead of creating a new one.

<Toaster system={{ dedupe: { enabled: true } }} /> toast.error('Payment failed', { toasterId: 'global', dedupeKey: 'payment-error' }); toast.error('Payment failed again', { toasterId: 'global', dedupeKey: 'payment-error' });

Dismiss reason via onClose (OFF by default)

When enabled in system, toast.onClose is called with a dismissal reason.

Possible reasons: timeout | swipe | close | cancel | action | dismiss.

<Toaster system={{ dismissReason: { enabled: true } }} /> `toast`('Saved', { onClose: (_toast, reason) => { console.log(reason); }, });

Quick reference

GoalWhat to do
Disable all toast haptics<Toaster haptics={false} />
Disable haptics for one toast“toast('…', { haptics: false })
Haptic on a button without a toast“triggerHaptic('success') from web-haptics-toast (Haptics)
Dedupe / update toasts<Toaster system={{ dedupe: { enabled: true } }} /> + dedupeKey on the toast
Dismissal reason in onClose<Toaster system={{ dismissReason: { enabled: true } }} />

Credits

  • Sonner — API and toast UX this project tracks.
  • Haptics — preset names and engine behavior are bundled in this package (no separate npm dependency). They follow the same vocabulary as the open web-haptics  project for familiarity, but you only depend on web-haptics-toast.
Last updated on