React Exit-Intent Panel
Genuinely useful for a real offer, genuinely hostile when overused. The restraint is built into the component rather than left to your discretion.
On a real page this fires once when the pointer leaves toward the browser chrome. Use the button to preview it here.
Installation
Props
| Prop | Type | Description |
|---|---|---|
| title | string | Headline |
| description | string | One line on what's being offered |
| cta | { label, href } | The offer |
| dismissLabel | string | Decline text (default "No thanks") |
| armAfterMs | number | Delay before detection arms (default 5000) |
| forceOpen | boolean | Render immediately, for testing |
| showTrigger | boolean | Manual trigger, used in these docs |
| onDismiss | () => void | Fires on any dismissal |
Four constraints, all deliberate
- Fires once. A
reflatches on first trigger. Re-firing on every pointer excursion is what makes these universally hated. - Armed on a delay. Nothing binds for
armAfterMs. Someone who lands and immediately leaves has not read anything; interrupting them is pure noise. - Top edge only. The handler ignores anything where
clientY > 0orrelatedTargetis set, so leaving sideways, downward, or into an iframe does not count. Only movement toward the browser chrome is exit intent. - Pointer devices only. Guarded by
matchMedia("(pointer: fine)"). There is no exit intent on touch — the equivalent implementations fire on scroll direction and interrupt people mid-read.
The decline is a plainly worded button of normal size, not a 9px guilt-tripped micro-link. If your offer needs a dark pattern to be accepted, the offer is the problem.
Make it worth the interruption: a real asset with no signup, as in the demo, not a discount on something they already declined. And if you also run a Sticky CTA Bar, pick one — a page with both is a page that fights its reader.
New components every week
Get the week's new Kelvin UI components and templates in one short email. No spam, unsubscribe anytime.