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
pnpm i web-haptics-toastAdd 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
| Goal | What 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.