@waitlistpack/react
v0.1.1
Published
Drop-in React component for the WaitlistPack waitlist signup widget. Shadow-DOM isolated, SSR-safe, zero CSS to import.
Maintainers
Readme
@waitlistpack/react
Drop-in React component for the WaitlistPack "Join the waitlist" signup form.
- Zero CSS to import. The form renders inside an open Shadow DOM, so its styling is fully isolated from your app's CSS reset, Tailwind preflight, etc.
- SSR-safe. Works inside Next.js (App Router & Pages), Remix,
Astro, Gatsby —
window/documentare only touched inuseEffect. - Tiny. ~2 KiB gzipped (excluding React). All widget logic is pulled from the same battle-tested core that powers the CDN bundle.
- React 17+ compatible.
Full guide (architecture, SSR, theming, events, publishing): see Framework widgets in this repo.
Install
npm install @waitlistpack/react
# or: pnpm add @waitlistpack/react
# or: bun add @waitlistpack/reactQuick start
import { WaitlistForm } from '@waitlistpack/react';
export default function HeroCta() {
return <WaitlistForm />;
}That's it. The component auto-detects window.location.hostname to
find the right waitlist and submits to the public WaitlistPack API.
Examples
Customise behaviour
<WaitlistForm
lang="en"
theme="dark"
showName
gdpr
source="hero-cta"
buttonText="Notify me"
onSuccess={({ email }) => {
analytics.track('waitlist_signup', { email });
}}
/>Self-hosted API
<WaitlistForm
apiUrl="https://api.your-domain.com"
domain="your-domain.com"
/>Multiple forms on one page (e.g. hero + footer)
<WaitlistForm source="hero" />
{/* … */}
<WaitlistForm source="footer" theme="light" />Each instance is fully independent — different config, different analytics attribution, no shared state.
Props
All props are optional.
| Prop | Type | Default | Description |
| -------------- | --------------------------------- | -------------------------------- | ----------- |
| apiUrl | string | https://api.waitlistpack.com | API origin |
| domain | string | window.location.hostname | Identifies the target waitlist |
| lang | 'en' \| 'zh' | browser preference | UI language |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | Colour scheme (auto follows OS) |
| showName | boolean | false | Show an optional name input |
| gdpr | boolean | false | Require a consent checkbox |
| redirect | string \| null | — | URL to navigate to after success |
| buttonText | string \| null | localised "Join" | Override the submit-button label |
| placeholder | string \| null | localised "[email protected]" | Override the email-input placeholder |
| successText | string \| null | localised default | Override the success message |
| source | string \| null | — | Sent as metadata.utm_source for attribution |
| onSuccess | (e: { email: string }) => void | — | Fired once on successful submission |
| className | string | — | Applied to the host <div> |
| style | CSSProperties | — | Applied to the host <div> |
| id | string | — | Applied to the host <div> |
Theming
The form is rendered inside an open Shadow DOM, which protects it
from host-page CSS but also means selectors like .wlp-button { … }
in your global stylesheet can't reach in. To restyle:
CSS custom properties (the easy way)
Custom properties cross the shadow boundary. Set them on the host element to tweak colours, radii, etc:
<WaitlistForm
style={{
// @ts-expect-error — CSS vars aren't in CSSProperties yet.
'--wlp-accent': 'hsl(280 80% 60%)',
'--wlp-radius': '4px',
}}
/>Available variables: --wlp-bg, --wlp-fg, --wlp-muted,
--wlp-border, --wlp-border-strong, --wlp-accent,
--wlp-accent-fg, --wlp-accent-hover, --wlp-error-bg,
--wlp-error-fg, --wlp-error-border, --wlp-success-bg,
--wlp-success-fg, --wlp-success-border, --wlp-radius,
--wlp-radius-sm.
::part selectors (full control)
The shadow root exposes parts on every visible element. Target them from anywhere in your global CSS:
waitlistpack-widget::part(root) {
background: black;
color: white;
}
waitlistpack-widget::part(title) {
font-family: 'Cal Sans', sans-serif;
}Available parts: root, title, subtitle, form, error,
success, footnote, brand.
Server-side rendering
The component is safe to render on the server — the underlying widget
only initialises inside useEffect, which doesn't run during SSR.
Next.js App Router: Works in both Server and Client Components.
The package ships with "use client" at the entry, so server
components can import it without explicit boundary wiring.
Remix / Gatsby / Astro / Eleventy: Same — the placeholder div is emitted during SSR and the widget mounts on hydration.
TypeScript
Full type definitions ship with the package. The widget's config shape is also re-exported in case you need it elsewhere:
import type {
WaitlistFormProps,
Lang,
Theme,
WidgetConfig,
} from '@waitlistpack/react';Development
bun install
bun run --filter @waitlistpack/react build
bun run --filter @waitlistpack/react typecheckThe package's runtime code comes from @waitlistpack/widget/core
(workspace dep) — see apps/widget/src/core.ts for the framework-
agnostic surface every framework binding consumes.
