
Exit Intent Popups
- 60 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Capture leaving visitors with targeted exit-intent popups showing offers and email-capture forms with frequency capping.
About
Uses dedicated popup apps to detect exit intent and display targeted offers with A/B testing and ESP integration. A developer uses it to recover abandoning visitors, capture emails, or test offer types.
- Per-platform popup tool recommendation table
- Frequency capping and A/B testing of offer types
Exit Intent Popups by the numbers
- 60 all-time installs (skills.sh)
- Ranked #1,276 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill exit-intent-popupsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 60 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Capture leaving visitors with targeted exit-intent popups showing offers and email-capture forms with frequency capping.
Files
Exit-Intent Popups
Overview
Exit-intent popups detect when a visitor is about to leave — by tracking rapid mouse movement toward the browser's address bar on desktop — and display a targeted offer. When implemented with proper targeting and frequency capping, they recover 5–10% of otherwise lost visitors. Dedicated popup apps handle all the detection logic, A/B testing, and ESP integration — no custom code needed.
When to Use This Skill
- When site bounce rate is above 60% and conversion rate is below 2%
- When capturing email addresses for the marketing list during the session
- When offering a first-purchase discount to new visitors who haven't converted
- When reminding checkout abandoners about items in their cart before they leave
- When A/B testing popup offer types (% off vs. free shipping vs. free gift)
Core Instructions
Step 1: Choose the right popup tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Klaviyo Forms (free, syncs to Klaviyo) or Privy ($30/mo) | Klaviyo Forms integrates directly with Klaviyo flows; Privy includes A/B testing, unique coupon codes, and detailed analytics |
| WooCommerce | OptinMonster ($9/mo) or Popup Maker (free) | OptinMonster has exit-intent detection, A/B testing, and WooCommerce integration; Popup Maker is simpler and free |
| BigCommerce | Justuno or Klaviyo Forms | Both have native BigCommerce integrations and exit-intent detection |
| Custom / Headless | Klaviyo Forms (via JavaScript embed) or custom implementation | Klaviyo Forms works on any site with a JavaScript snippet |
Step 2: Configure exit-intent detection and targeting
Most popup tools have exit-intent built in. The key is configuring it correctly to avoid annoying visitors.
---
Shopify with Klaviyo Forms
1. In Klaviyo, go to Sign-up Forms → Create Form 2. Choose Popup as the form type 3. Under Targeting, set:
- Display when: Exit intent (mouse moves to top of browser)
- Delay: 5 seconds minimum time on page before activating
- Frequency: Show once per 14 days (Klaviyo manages this via cookie)
4. Under Targeting → Conditions, add:
- Show only on: Homepage, product pages, collection pages
- Do NOT show on: /checkout, /cart, /account
- Do NOT show to: Existing subscribers (Klaviyo checks this automatically for identified profiles)
5. Under Form Content, set your offer:
- For email capture: "Get 10% off your first order" + email input + GDPR consent checkbox
- For cart reminder: "You have items in your cart — complete your purchase" + link back to cart
6. Under Success Action, create a discount code:
- Connect to Shopify → Discounts — Klaviyo can automatically create unique single-use 10% off codes
- These codes appear in the success message and are automatically synced to the subscriber's profile
---
Shopify with Privy
1. Install Privy from the Shopify App Store 2. Go to Privy → Displays → New Display → Popup 3. Under When to show, select "Exit intent" 4. Under Who to show it to, configure:
- New visitors only (suppress for logged-in customers)
- Minimum 30 seconds on site
- Not in checkout
- Frequency cap: 14 days
5. Under Offers, Privy automatically generates unique coupon codes synced to Shopify's discount system 6. Connect Privy to Klaviyo or Mailchimp under Integrations for automatic list sync
---
WooCommerce with OptinMonster
1. Install OptinMonster plugin from the WordPress directory 2. Create a new campaign: Popup → Exit Intent 3. Under Display Rules, configure:
- Exit Intent trigger: sensitivity = Medium
- Time on page: minimum 30 seconds
- Page targeting: include homepage, shop, product pages; exclude checkout, cart, my-account
- Frequency: 14-day cookie suppression (OptinMonster default)
4. Under Integrations, connect to Mailchimp, Klaviyo, or your email provider 5. For unique coupon codes: use WooCommerce → Coupons to create a coupon, then display the code in the success message (OptinMonster does not auto-generate unique codes on free tiers)
---
BigCommerce with Justuno
1. Install Justuno from the BigCommerce App Marketplace 2. Go to Justuno → Promotions → New Popup 3. Configure exit intent triggers, targeting rules, and A/B test variants through the visual builder 4. Justuno integrates with Klaviyo and Mailchimp for list sync
---
Custom / Headless
For custom builds, Klaviyo Forms works on any website:
1. Add the Klaviyo JavaScript snippet to your site 2. Go to Klaviyo → Sign-up Forms → Create Form and configure as above 3. Klaviyo handles exit detection, frequency capping, and list sync
If you need full control over the popup behavior (custom exit detection logic):
function initExitIntent({ threshold = 20, delay = 5000, onExitIntent }) {
let activated = false;
setTimeout(() => {
document.addEventListener('mouseleave', (e) => {
if (e.clientY <= 0 && !activated) {
activated = true;
onExitIntent();
}
});
}, delay);
// Mobile: use visibilitychange instead of mouseleave
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden' && !activated) {
activated = true;
onExitIntent();
}
});
}
// Check frequency cap using localStorage
function shouldShowPopup(popupId, capDays = 14) {
const lastSeen = localStorage.getItem(`popup_seen_${popupId}`);
if (!lastSeen) return true;
return (Date.now() - parseInt(lastSeen)) > capDays * 86400000;
}Step 3: Configure the offer and A/B test
The most impactful thing to test is the offer type. Run one A/B test at a time:
| Offer Type | Best For | Typical Email Capture Rate |
|---|---|---|
| 10% off first order | General merchandise | 3–8% of popup views |
| Free shipping | High-AOV stores (shipping is expensive) | 4–9% of popup views |
| Free gift with purchase | Beauty, supplements | 5–10% of popup views |
| Content offer ("Download our guide") | B2C brands with content | 2–5% |
In Klaviyo/Privy: create two variants of your form with different offers, split traffic 50/50, and measure conversion rate over 2 weeks before picking a winner.
Step 4: Generate unique discount codes (not generic codes)
Generic codes like WELCOME10 get shared on coupon sites and inflate your attribution:
- Shopify + Klaviyo: Klaviyo automatically generates unique single-use Shopify discount codes — enable this in the form's success action settings
- Shopify + Privy: Privy connects to Shopify's discount system and generates unique codes automatically
- WooCommerce: Use the WooCommerce Unique Coupon Generator plugin, or create a general code with per-customer usage limit
Best Practices
- Always include a control/holdout group (20–30%) — without a holdout, you cannot tell if the popup is adding value or just capturing conversions that would have happened anyway (most popup apps support this)
- Delay activation by at least 5 seconds — popups that fire immediately train visitors to close them without reading
- Frequency cap at 14 days minimum — all major popup apps handle this via cookie automatically; verify it is enabled
- Never pre-check the email consent checkbox — GDPR requires explicit opt-in; pre-checked boxes are not valid consent in the EU
- Generate unique one-time codes — generic codes (WELCOME10) get shared on coupon sites
- Exclude already-subscribed visitors — Klaviyo checks this automatically; for other tools, suppress via cookie
Common Pitfalls
| Problem | Solution |
|---|---|
| Popup fires immediately on page load | Set minimum 5-second delay AND minimum scroll depth (20%) in targeting settings |
| Popup shown to same user on every visit | Verify frequency capping is enabled in your popup app (Klaviyo: 14 days; Privy: configurable; OptinMonster: configurable) |
| Exit intent fires on mobile | Switch to scroll-based or time-based triggers for mobile; most tools do this automatically when exit-intent is selected |
| No way to measure whether the popup helps conversion | Enable A/B testing with a control group in your popup app; most support this natively |
| High popup-driven unsubscribe rate | The offer is not compelling enough; test free shipping vs. % off; also check that emails are sending too frequently after signup |
Related Skills
- @cart-abandonment-recovery
- @email-marketing-automation
- @conversion-rate-optimization
- @push-notifications
- @first-party-data-collection
{
"context": "Tests whether the agent correctly implements holdout-group A/B testing with deterministic session-based variant assignment, tracks control group exit events, uses unique one-time promo codes, enforces single-variable testing, and produces a correct holdout comparison SQL query.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Control variant is null",
"max_score": 10,
"description": "The variants map/object has a 'control' key whose value is null (no popup for the holdout group)"
},
{
"name": "Deterministic session assignment",
"max_score": 10,
"description": "Variant assignment uses the last 2 characters of a session ID parsed as hex: parseInt(sessionId.slice(-2), 16) % numVariants"
},
{
"name": "Control group event tracking",
"max_score": 10,
"description": "Even for the control (no popup) group, exit intent is tracked — an event is recorded with shown: false or equivalent"
},
{
"name": "Unique one-time promo codes",
"max_score": 10,
"description": "Promo codes are generated individually per email submission (not a shared static code like WELCOME10)"
},
{
"name": "Promo code source field",
"max_score": 8,
"description": "Generated promo codes include a source field using exit_popup_${popupId} pattern"
},
{
"name": "Promo code expiry",
"max_score": 8,
"description": "Generated promo codes have an expiry (expiresInDays or equivalent) set to 7 days"
},
{
"name": "Single variable enforcement",
"max_score": 8,
"description": "Experiment variants only differ on offer type — the experiment-design.md or code comments explain that only one variable (offer type) is rotated at a time"
},
{
"name": "SQL groups by variant",
"max_score": 8,
"description": "The analytics SQL query groups results by variant and computes conversion rate per variant"
},
{
"name": "SQL holdout comparison",
"max_score": 8,
"description": "The SQL query queries from exit_intent_events (or equivalent table) and includes sessions from the control group"
},
{
"name": "Email capture stores source",
"max_score": 10,
"description": "The email capture handler stores source='exit_popup' (or similar) alongside the subscriber record"
},
{
"name": "Cleanup after popup shown",
"max_score": 10,
"description": "The exit intent cleanup function is called after the popup is displayed (to prevent re-triggering)"
}
]
}
Exit-Intent Popup A/B Test and Offer Backend
Problem/Feature Description
A growth team at a direct-to-consumer supplement brand has been running an exit-intent popup for three months with a 10% discount code "WELCOME10". Their CMO is skeptical that the popup is actually driving incremental revenue — a consultant pointed out that visitors who were already going to convert are just using the generic code for a discount they didn't need. The team also can't tell whether the popup is responsible for their email list growth or whether those signups would have happened anyway via the footer form.
The engineering team has been asked to rebuild the experiment infrastructure: implement proper A/B variant assignment so the same user consistently sees the same variant, include a group that sees no popup at all so the team can measure true incremental impact, and replace the shared promo code with individually generated codes. There's also a dispute about whether the team is testing too many things at once — their designer wants to simultaneously test headline copy, offer type, and background color. The engineering lead wants the system to enforce single-variable testing.
Output Specification
Produce the following files:
exit-intent-variants.ts: variant assignment and experiment orchestration logicpopup-api.ts: a mock server-side handler (TypeScript) for the email capture endpoint, showing how subscribers and promo codes are handledanalytics-query.sql: the SQL query to compare conversion rates across variants and the holdout groupexperiment-design.md: a short document explaining the variant setup, how assignment works, and why the experiment is structured the way it is
{
"context": "Tests whether the agent correctly implements the exit-intent detection module using the prescribed mouse tracking logic, mobile fallback, delay pattern, and cleanup function structure.",
"type": "weighted_checklist",
"checklist": [
{
"name": "mousemove threshold check",
"max_score": 10,
"description": "Checks e.clientY against a threshold (default 20) — the condition is e.clientY < threshold"
},
{
"name": "movementY direction check",
"max_score": 10,
"description": "Also checks e.movementY < 0 on the mousemove handler (upward movement toward top of viewport)"
},
{
"name": "mouseleave clientY check",
"max_score": 10,
"description": "Registers a mouseleave listener that fires when e.clientY <= 0 (cursor left through top of viewport)"
},
{
"name": "Activation delay",
"max_score": 10,
"description": "Both the mouseleave and mousemove listeners are registered inside a setTimeout with a delay (default 3000ms), not immediately"
},
{
"name": "Default delay value",
"max_score": 8,
"description": "The default delay value is 3000 (milliseconds) when not specified by the caller"
},
{
"name": "Default threshold value",
"max_score": 8,
"description": "The default threshold value is 20 (pixels) when not specified by the caller"
},
{
"name": "Mobile visibilitychange",
"max_score": 12,
"description": "Registers a visibilitychange listener that triggers when document.visibilityState === 'hidden' for mobile support"
},
{
"name": "Single activation guard",
"max_score": 8,
"description": "Uses an 'activated' boolean flag to ensure onExitIntent is called at most once"
},
{
"name": "Cleanup function returned",
"max_score": 12,
"description": "The init function returns a cleanup function that calls clearTimeout and removes the mousemove event listener"
},
{
"name": "TypeScript interface exported",
"max_score": 12,
"description": "Exports a TypeScript interface or type for the configuration options (threshold, delay, onExitIntent)"
}
]
}
Exit Intent Detection Module
Problem/Feature Description
The engineering team at a mid-sized e-commerce company has been asked to build native exit-intent detection from scratch — without any third-party libraries like Ouibounce or ExitMonitor. The marketing team wants full control over the trigger logic, and the current vendor solution costs $800/month. The new module needs to work reliably on both desktop and mobile browsers, and must not cause any jarring experience for visitors who just loaded the page.
A previous attempt by a junior developer fires the popup the moment anyone moves their mouse, which has caused a surge in bounce rate and complaints. The module needs to be well-structured, written in TypeScript, and designed so it can be cleanly removed or re-initialized without leaving dangling event listeners.
Output Specification
Write a TypeScript file exit-intent.ts that exports:
- A function to initialize exit-intent detection
- An interface or type for the configuration options
The module should handle both desktop and mobile detection scenarios. Include a brief README.md explaining the configuration options and how to integrate the module.
{
"context": "Tests whether the agent correctly implements frequency capping with localStorage, targeting rules (login check, checkout exclusion, time-on-page, already-subscribed), and GDPR-compliant popup UI with correct aria attributes.",
"type": "weighted_checklist",
"checklist": [
{
"name": "localStorage frequency key format",
"max_score": 8,
"description": "Frequency capping uses localStorage with key format popup_seen_${popupId} (template literal with popup ID)"
},
{
"name": "Dismiss sets dismissed flag",
"max_score": 8,
"description": "On explicit dismiss, also writes popup_dismissed_${popupId} key to localStorage (separate from the seen key)"
},
{
"name": "Frequency cap duration",
"max_score": 8,
"description": "Frequency cap suppresses the popup for at least 14 days (not less)"
},
{
"name": "Logged-in customer exclusion",
"max_score": 8,
"description": "Checks document.cookie for 'customer_id=' to exclude already logged-in customers"
},
{
"name": "Checkout page exclusion",
"max_score": 8,
"description": "Excludes pages where window.location.pathname starts with '/checkout'"
},
{
"name": "Time on page minimum",
"max_score": 8,
"description": "Requires at least 30000ms (30 seconds) time on page before showing the popup"
},
{
"name": "Existing subscriber exclusion",
"max_score": 8,
"description": "Checks for a subscriber_id cookie or localStorage flag to exclude already-subscribed visitors"
},
{
"name": "Popup dialog role",
"max_score": 8,
"description": "Popup container element has role='dialog' and aria-modal='true'"
},
{
"name": "Close button aria-label",
"max_score": 8,
"description": "Close button has aria-label='Close'"
},
{
"name": "Consent checkbox not pre-checked",
"max_score": 10,
"description": "The email consent checkbox does NOT have defaultChecked or checked={true} — it starts unchecked"
},
{
"name": "Consent checkbox required",
"max_score": 10,
"description": "The consent checkbox has the 'required' attribute so the form cannot be submitted without checking it"
},
{
"name": "Offer type support",
"max_score": 8,
"description": "The popup component handles at least two of: 'percent_off', 'free_shipping', 'free_gift' offer types"
}
]
}
Email Capture Popup with Targeting and Compliance
Problem/Feature Description
A European fashion retailer is launching a new online store and wants to capture email addresses from visitors who are about to leave the site. Their legal team has flagged that several competitors have been fined for non-compliant email marketing signup flows. The team wants a React popup component and the surrounding display logic that is both effective and legally sound for EU markets.
The popup should only show to visitors who are worth targeting — the team has noticed that showing it to everyone, including customers mid-checkout and already-logged-in users, has caused significant annoyance. They also discovered that frequent re-display of the popup to return visitors is hurting their brand perception. The solution needs to suppress display for visitors who've already seen it recently, handle the case where a visitor explicitly closes the popup differently from simply navigating away, and respect visitors who are subscribed already.
Output Specification
Produce the following TypeScript/React files:
popup-display.ts: the logic to determine whether to show the popup and manage frequency state in browser storageExitIntentPopup.tsx: the React component for the popup UI
Include a brief integration-notes.md describing the targeting rules implemented and the compliance considerations addressed.
{
"name": "finsi/exit-intent-popups",
"version": "0.1.0",
"summary": "Exit-intent detection with offer targeting, frequency capping, and A/B testing",
"skills": {
"exit-intent-popups": {
"path": "SKILL.md"
}
}
}