Upgrade gate
ProLocks a feature behind a plan and says how to get it: over a dimmed preview, as a row in settings, or in place of the feature, with a plan comparison and a custom upgrade action. PlanBadge marks locked items in menus and nav.
npx prodular-ui add upgrade-gatePart of Prodular UI Pro. Preview and read the docs freely, install with a license. See pricing
Over a preview
The feature shows dimmed and cannot be used. Compare plans opens a table with what the upgrade adds.
Cash flow forecast
Included in ScaleSee the next 13 weeks of cash from open invoices, bills and payroll, updated every night.
"use client"
import { ChartNoAxesCombinedIcon } from "lucide-react"
import { UpgradeGate, type PlanComparison } from "@/components/ui/upgrade-gate"
const comparison: PlanComparison = { currentPlanId: "growth", targetPlanId: "scale", plans: [ { id: "growth", name: "Growth", price: "$39 per seat" }, { id: "scale", name: "Scale", price: "$69 per seat" }, ], rows: [ { label: "Invoices a month", values: { growth: "2,500", scale: "Unlimited" } }, { label: "Cash flow forecast", values: { growth: false, scale: true } }, { label: "Multiple entities", values: { growth: false, scale: true } }, { label: "Audit log and SSO", values: { growth: false, scale: true } }, { label: "Approval workflows", values: { growth: true, scale: true } }, ],}
const bars = [42, 55, 48, 61, 58, 70, 66, 74, 69, 82, 78, 88]
export default function UpgradeGateOverlay() { return ( <UpgradeGate locked feature="Cash flow forecast" plan="Scale" description="See the next 13 weeks of cash from open invoices, bills and payroll, updated every night." icon={<ChartNoAxesCombinedIcon />} comparison={comparison} onUpgrade={() => new Promise<void>((r) => setTimeout(r, 900))} className="w-full max-w-2xl border bg-card" > <div className="flex flex-col gap-4 p-6"> <div className="flex flex-col gap-1"> <span className="text-[13px] text-muted-foreground">Cash at Jan 26</span> <span className="text-2xl font-semibold tabular-nums">$1,284,900</span> </div> <div className="flex h-40 items-end gap-2"> {bars.map((h, i) => ( <span key={i} className="flex-1 rounded-t-sm bg-chart-1/70" style={{ height: `${h}%` }} /> ))} </div> </div> </UpgradeGate> )}Rows in settings
variant inline next to settings people already have. blockedReason replaces the button for people who cannot upgrade.
"use client"
import { Switch } from "@/components/ui/switch"import { UpgradeGate } from "@/components/ui/upgrade-gate"
export default function UpgradeGateSettings() { return ( <div className="flex w-full max-w-xl flex-col divide-y rounded-xl border bg-card px-5 shadow-xs"> <div className="flex items-center gap-4 py-3"> <div className="flex min-w-0 flex-1 flex-col gap-0.5"> <span className="text-sm font-medium">Payment reminders</span> <span className="text-[13px] text-muted-foreground">Email customers 3 days before and after the due date.</span> </div> <Switch defaultChecked aria-label="Payment reminders" /> </div> <UpgradeGate locked variant="inline" feature="Approval workflows" plan="Growth" description="Route bills over a limit to the right approver before they are paid." onUpgrade={() => new Promise<void>((r) => setTimeout(r, 700))} /> <UpgradeGate locked variant="inline" feature="Single sign-on" plan="Scale" description="Sign in with Okta, Google Workspace or Microsoft Entra." blockedReason="Ask an owner to upgrade" onUpgrade={() => {}} /> </div> )}In place of a page
variant replace for a whole area. The upgrade fails here to show the error.
Agents
Included in GrowthLet agents chase overdue invoices and prepare payouts, with you approving every action.
"use client"
import { BotIcon } from "lucide-react"
import { UpgradeGate } from "@/components/ui/upgrade-gate"
export default function UpgradeGateReplace() { return ( <UpgradeGate locked variant="replace" feature="Agents" plan="Growth" description="Let agents chase overdue invoices and prepare payouts, with you approving every action." icon={<BotIcon />} onUpgrade={() => new Promise<void>((_, reject) => setTimeout(() => reject(new Error("Your card was declined. Update it in Billing and try again.")), 800))} className="w-full max-w-xl" /> )}Anatomy
<UpgradeGate locked={!entitlements.forecast} feature="Cash flow forecast" plan="Scale" onUpgrade={openCheckout}> <Forecast /></UpgradeGate>Guidance
Use it when
- Features people can see but their plan does not include: show what they get, then how to get it.
- inline in settings lists and forms, overlay over a preview of a report or chart, replace for a whole section.
- blockedReason for members who cannot change billing, so they know who can.
Avoid
- Hiding features entirely. People cannot want what they never see.
- Gating on every click. Gate once at the feature's entrance.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Decide locked from the workspace's entitlements on the server and pass it in; the gate does no checks of its own.
- Name the feature and plan exactly as on the pricing page. description says what the feature does for them, in one sentence.
- onUpgrade opens checkout or changes the plan; throw with a message to show a failure. Use PlanPicker for seat and period choices.
- Pass comparison with the current and target plans and only the rows that differ, plus one or two they already have for context.
- The overlay preview is inert and hidden from assistive tech; keep real data out of it if the plan should not reveal it.
API
UpgradeGate
| Prop | Type | Default | Description |
|---|---|---|---|
| locked | boolean | Shows the gate; false renders children. | |
| feature / plan / description | string / string / ReactNode | What is locked, which plan has it and why it matters. | |
| variant | "overlay" | "inline" | "replace" | "overlay" | How the lock shows. |
| onUpgrade / upgradeLabel | () => Promise<void> / string | Upgrade to {plan} | The upgrade action. |
| comparison | PlanComparison | Adds Compare plans with a feature table. | |
| blockedReason | ReactNode | Replaces the button for people who cannot upgrade. |
PlanBadge
| Prop | Type | Default | Description |
|---|---|---|---|
| plan | string | A lock with the plan name for menus and nav. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add upgrade-gate# or, with the registry in components.jsonnpx shadcn@latest add @prodular/upgrade-gate