React components
Drop-in paywall UI for crxpay subscriptions — six components, one provider, no design system required.
@crxpay/react-paywall is the official React layer for rendering subscription
UI on top of @crxpay/sdk. Six components, five hooks, one CSS file. Themed by
two CSS variables. Install once, ship a paywall in an afternoon.
Why a separate package
The SDK gives you state and methods (getSubscription, openCheckout,
hasEntitlement). It doesn't render anything. @crxpay/react-paywall is the
opinionated React UI on top — useful when you want a paywall now without
hand-rolling Tailwind cards, Stripe redirects, modal focus traps, theming,
and trial countdowns yourself.
Use the SDK directly when you want full control over the UI. Use the components when you want a paywall to ship today.
What ships
Components
| Component | What it does |
|---|---|
<CrxPayProvider> | Root wrapper. Initializes the right transport (extension vs web), exposes context, applies theme as CSS variables. Wrap your tree once. |
<UpgradeButton> | One-tap CTA that opens Stripe Checkout for a price. Hides itself when the customer is already paying. |
<PricingTable> | Auto-fetches offerings, renders plan cards, highlights the recommended (yearly) package, computes "Save X%" automatically. |
<PaywallModal> | Full-screen plan picker with focus trap + Esc + body-scroll lock. Trigger from anywhere via usePaywall().open(). |
<TrialBadge> | Auto-counting "X days left" pill. Hides itself when not trialing. Shifts to a danger style when ≤ 3 days remain. |
<ManageSubscriptionLink> | Anchor that opens the Stripe Billing Portal. Hides if the customer isn't paying. |
<EntitlementGate> | Wrap premium UI. Renders children when entitled, fallback otherwise. Default fallback opens the PaywallModal. |
Hooks
| Hook | What it returns |
|---|---|
useCrxPay() | Raw context — transport, subscription, theme, paywallOpen state. |
useSubscription() | { subscription, loading, isActive, isTrialing, refresh } |
useEntitlement(id) | { granted, loading } — the feature-gate primitive. |
useOfferings() | { offerings, current, loading, refresh } — products + prices. |
usePaywall() | { isOpen, open, close } — programmatic modal control. |
Quick install
pnpm add @crxpay/react-paywall
import { CrxPayProvider, PricingTable } from '@crxpay/react-paywall';
import '@crxpay/react-paywall/styles.css';
export default function Popup() {
return (
<CrxPayProvider apiKey="crxpay_pub_…">
<h1>Upgrade to Pro</h1>
<PricingTable />
</CrxPayProvider>
);
}
That's the whole integration. The provider auto-detects whether it's running in a Chrome extension or a web app, and routes calls through the right transport without any extra config.
→ Full setup walk-through: Quickstart → Brand customization: Theming → Every prop and hook: API reference
Bundle size
dist/index.esm.js 24 kB (7.1 kB gz) — JS
dist/styles.css 9 kB — CSS, opt-in
Tree-shakable: components you don't import get dropped.
framer-motion is a peer dep — most React apps already have it. If not,
add ~30 kB.
What it doesn't do
- No router. Components don't navigate; they call SDK methods like
openCheckoutwhich open Stripe Checkout in a new tab. - No state management. Subscription state lives in the SDK's signed cache (extension) or transport memory (web). Components subscribe via the provider.
- No global CSS reset. Styles are scoped under
[data-crxpay-root]. You can keep using Tailwind, shadcn, or anything else side-by-side. - No custom design system. If you want to hand-roll your own paywall UI on top of the SDK, that's fine — the components are not required to use crxpay.
Was this page helpful?
Your feedback shapes what we document next.