shadcn.io is not affiliated with official shadcn/ui
Subscription Required Error Block for React Aria
A full-screen 403 for a feature the plan does not include: the facts, what Pro adds, and an editable message that asks the workspace owners, with a Viewing as demo control that turns the ask into an upgrade with the price worked out.
Installation
Overview
Tell people why a feature is locked and who can unlock it, with this subscription error page for React with React Aria. It's the 403 screen of Funnelwright, a conversion-analytics tool: a whole screen with the page's rails and one band across it, split by a divider. At the start, the facts in rows ruled by Guides (Your plan: Free, Needed for Funnels: Pro, Your role: Member), what Pro adds in five rows, and a demo control, a Toggle Group that sets Viewing as to Member or Owner. At the end is the ask: the two workspace owners as Avatar rows, a Textarea in a Field that already holds a message written for them and can be edited, a Copy Button that really writes it to the clipboard, and a Shine Button that sends the request. Viewed as an owner, the same cell becomes Upgrade to Pro with the price worked out in whole cents. It's built from Toggle Group, Avatar, Textarea, Field, Copy Button, Button, Shine Button and Guides, with a CSS entrance only. For any product whose paid features sit behind people who can't change billing.
FAQ
Why does the page ask an owner instead of showing a plan comparison?
Because a member can't buy anything: the way to unblock them is a person, so the page names the two people who can and writes the message for them. It says what the plan is, what is needed and what the visitor's role is, then stops. A comparison of plans belongs on your pricing and billing pages, where someone who can act is looking at it.
What does Send the request do, and what stays in the page?
It checks that the message says something (an empty one is named under the field and the field is focused), then records the request in the page's state and says "Recorded in this demo for Odile and Teodor. Nothing was sent." Undo takes it back, and a second press says it is already recorded. To wire it for real, send the text to the owners' billing contact from your server where the code says so, and show the pending state from the response.
What is the Viewing as control for, and how is the price worked out?
It is a demo control: your app knows the visitor's role, so a real page shows one side only. Switching it clears any result, so the two states never mix. As an owner you see the price per member and for the workspace, both from PRO_CENTS (2400) and MEMBERS (6) in whole cents, formatted with Intl.NumberFormat, so 6 times $24.00 is $144.00 with no rounding. Upgrade to Pro records the upgrade the same way and says nothing was charged; replace it with a call to your billing provider.
Does Copy message really copy?
Yes: it writes the text as it is now, including your edits, to the clipboard, and says so in the page's status line. If the browser refuses (no permission, or a page that isn't served over HTTPS) the line says that instead, and the text stays in the field to select by hand.
Does this return a 403 status?
No: a component can't. Send the real HTTP status from your server, a 403 for a role that can't change the plan, or whichever status your API uses for plan limits, so clients and crawlers don't see a 200 with an error drawn on it.
How does it look on a phone?
The ask comes right after the facts, and the button is pinned to the foot of the screen until you reach it. What Pro adds shows its first two rows with one constant button, All five Pro features, that opens the rest (it announces whether it is expanded). The longer sentence under the heading is kept for screen readers only.