react-marketing-popups
v1.0.0
Published
Headless React Popout components and trigger hooks
Readme
react-marketing-popups
A lightweight, framework-agnostic React UX library for high-converting popouts, banners, slide-ins, and timed/behaviour-based triggers.
Designed for modern React apps and built around smooth animations, configurable triggers, and persistent behaviour (per-user seen states).
Perfect for marketing teams, e-commerce flows, onboarding funnels, exit-intent modals, upsells, and announcements.
Features
- Popouts (Modal)
- Banners (Full-width horizontal; full-height vertical)
- SlideIn panels
- Built-in triggers
- Timer
- Scroll %
- Inactivity
- Exit Intent
- Persistence layer (
localStorage) - “Core” components without triggers
- Hooks exported for custom advanced flows
Installation
npm install react-marketing-popups
# or
yarn add react-marketing-popups
Quick Start Example
High-level components with built-in trigger and persistence
import { Popout } from "react-marketing-popups";
export default function Example() {
return (
<Popout
trigger="timer"
triggerProps={{ ms: 4000 }}
open={false}
onOpenChange={(v) => console.log("Open:", v)}
>
<div style={{ padding: 20 }}>Special Offer: 10% Off Today Only!</div>
</Popout>
);
}Core components (manual)
With persistence
import { useEffect, useState } from "react";
import { PopoutCore, useTimerTrigger, usePersistence } from "react-marketing-popups";
export default function Example() {
const [open, setOpen] = useState(false);
const [ok, setOk] = useState(false);
const [fired] = useTimerTrigger();
const { hasSeen, markSeen } = usePersistence('popout-test');
useEffect(() => {
if (fired && !hasSeen()) setOpen(true);
if (ok) markSeen();
if (fired && !open) markSeen();
}, [fired, ok]);
return (
<Popout
id="popout-test"
open={open}
onOpenChange={(val) => setOpen(val)}
>
<div style={{ padding: 20 }}>
<h4>
Special Offer: 10% Off Today Only!
</h4>
<button onClick={() => setOk(true)}>Claim offer</button>
</div>
</Popout>
);
}Without persistence
import { useEffect, useState } from "react";
import { Popout, useTimerTrigger, usePersistence } from "react-marketing-popups";
export function Example() {
const [open, setOpen] = useState(false);
const [ok, setOk] = useState(false);
const [fired] = useTimerTrigger();
useEffect(() => {
if (fired && !open) setOpen(true);
if (ok) setOpen(false);
}, [fired, ok]);
console.log(open)
return (
<Popout
id="popout-test"
open={fired && open}
onOpenChange={(val) => {
setOpen(val)
}}
>
<div style={{ padding: 20 }}>
<h4>
Special Offer: 10% Off Today Only!
</h4>
<button onClick={() => setOk(true)}>Claim offer</button>
</div>
</Popout>
);
}You can see more demos using storybook or if you need an advanced demo, please create an issue.
Components Overview
The library includes three components:
- Popout – modal centered on screen
- Banner – full-width horizontal banner (top or bottom) or full-height vertical banner (left or right)
- SlideIn – horizontal panel sliding in from left or right
Each has:
- A Core Component (manual control; no triggers or persistence)
- A set of Trigger Components (auto-controlled via behaviour and coupled with persistence)
- All components have these exportable as
import { PopoutByTimer } from 'react-marketing-popups, we will use Popout for the examples.
- All components have these exportable as
- A general
<ComponentIndex>component that is defined with a trigger and a wrapper around the components
Shared Props Table
All components share these props. Asterisked props are required
| Prop | Type | Description | Default |
| --- | --- | --- | --- |
| id* | string | Unique key for persistence tracking |
| open* | boolean | Control whether the popout is visible | false
| onOpenChange* | (open: boolean) => void | Called when the popout opens/closes |
| children* | ReactNode | Content inside the modal |
| trigger | "timer", "scroll", "exit", "inactivity" | Selects a trigger component |
| triggerProps | Varies per trigger | Configuration for the selected trigger |
| isOk | boolean | Signals the user took desired action | false |
| closeOnOk | boolean | Duration of animation in ms | false |
| duration | number | Duration of animation in ms | 300 |
| closeBtnClassname | string | Duration of animation in ms |
1. Popout (Modal)
Import
import { Popout } from "react-marketing-popups";Description
A smooth animated modal that can open manually or via a marketing trigger (timer, scroll, inactivity, exit intent).
Props Table
| Prop | Type | Description | Default |
| --- | --- | --- | --- |
| lockScroll | boolean | Locks body scroll while open | false
| closeOnOverlay | boolean | Close modal on overlay click | true
| overlayClassName | boolean | className for overlay element | true
| contentClassName | boolean | className for content container element | true
| elemProps | { overlayElProps?: typeof HTMLDivElement, containerElProps?: typeof HTMLDivElement } | Props for overlay element and content container element | true
| animation | "fade", "zoom", "bounce" | Animation effect used for open and close of component | "zoom"
Popout Core Component (No Triggers)
import { PopoutCore } from "react-marketing-popups";
<PopoutCore open={open} onOpenChange={setOpen}>
Content
</PopoutCore>Use this when you want to control the trigger, and the persistence manually.
Popout Trigger Wrapper Components
1. PopoutByTimer
Opens after a specified delay.
import { PopoutByTimer } from "react-marketing-popups";
<PopoutByTimer
{...props}
triggerProps={{ ms: 3000, enabled: true }}
>
Timer Popout
</PopoutByTimer>Trigger Props
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| ms | number | Delay before firing | 3000
| enabled | boolean | Whether timer should run | true
2. PopoutByScroll
Opens when user scrolls past a percentage of the page.
import { PopoutByScroll } from "react-marketing-popups";
<PopoutByScroll
{...props}
triggerProps={50}
>
Scroll Offer
</PopoutByScroll>Trigger Props
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| percent | number | Scroll depth threshold | 50
3. PopoutByExit
Opens when the user shows exit-intent.
<PopoutByExit {...props}>
Are you leaving already?
</PopoutByExit>No triggerProps required.
4. PopoutByInactivity
Opens after inactivity timeout.
<PopoutByInactivity
{...props}
triggerProps={{ ms: 10000 }}
>
Still there? Here's a bonus!
</PopoutByInactivity>Trigger Props
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| ms | number | Inactivity duration | 30000
2. Banners
Full-width horizontal or full-height vertical marketing banners.
Import
import { Banner } from "react-marketing-popups";Example
<Banner open={open} onOpenChange={setOpen} position="bottom">
Free Shipping Ends Today!
</Banner>Props Table
| Prop | Type | Description | Default |
| --- | --- | --- | --- |
| position | "top", "bottom", "left" or "right" | Banner placement | "bottom" |
| animation | "fade", "slide" or "bounce" | Animation effect | "slide" |
| containerClassName | boolean | className for root element |
| contentClassName | boolean | className for content container element |
| elemProps | { containerElProps?: typeof HTMLDivElement, containerElProps?: typeof HTMLDivElement } | Props for overlay element and content container element |
3. SlideIn
A fixed left or right panel.
Import
import { SlideIn } from "react-marketing-popups";Example
<SlideIn open={open} onOpenChange={setOpen} position="right">
Checkout our new feature!
</SlideIn>Props Table
| Prop | Type | Description | Default |
| --- | --- | --- | ---|
| position | "left" or "right" | Slide direction | "left" |
| animation | "fade", "slide" or "bounce" | Animation effect | "slide" |
| wrapperClassName | boolean | className for root element |
| containerClassName | boolean | className for container element |
| contentClassName | boolean | className for content container element |
| elemProps | { wrapperElProps?: typeof HTMLDivElement; containerElProps?: typeof HTMLDivElement, containerElProps?: typeof HTMLDivElement } | Props for overlay element and content container element |
Hooks
The library exports several standalone hooks that can be used directly.
useTimerTrigger(ms, enabled)
Triggers once after timeout.
| Arg | Type | Description | Default |
| --- | --- | --- | --- |
| ms | number | Time before firing | 3000
| enabled | boolean | Should it run | true
Example
const [fired] = useTimerTrigger(3000, true);useScrollTrigger(percent)
Triggers on scroll %.
| Arg | Type | Description | Default
| --- | --- | --- | --- |
| percent | number | Scroll depth threshold | 50
Example
const [fired] = useScrollTrigger(60);useInactivityTrigger(ms)
Triggers after inactivity.
| Arg | Type | Description | Default
| --- | --- | --- | --- |
| ms | number | Time for user to be inactive before displaying | 30000
Example
const [fired] = useInactivityTrigger(10000);useExitIntentTrigger()
Triggers when mouse leaves viewport top edge.
const [fired] = useExitIntentTrigger();usePersistence(key)
LocalStorage "hasSeen" tracking helper.
| Arg | Type | Description | Default
| --- | --- | --- | --- |
| key | string | Id |
Returned API
| Method | Description |
| --- | --- |
| hasSeen() | Returns true if key marked as seen |
| markSeen() | Marks key as seen |
| clear() | Removes key |
Example
const { hasSeen, markSeen, clear } = usePersistence("popout-1");Storybook
Launch storybook with npm run storybook
License
N/A
Contributions
PRs, issues, feature requests, and improvements are welcome!
Implement tree-shaking
