shadcn.io is not affiliated with official shadcn/ui
Plan Upgrade Modal Billing Block for Radix UI
A workspace page with an upgrade dialog that knows the role: an owner sees what Pro adds, the pro-rated charge and Upgrade now; a member sees the same facts and a note to the owner with Send request.
Installation
Overview
Put the right next step in front of whoever hits a locked feature, with this upgrade dialog for React with Radix UI. It opens from the Workspace page of Nookbook, a team wiki: the page's header holds an outline Upgrade Button and the page is drawn under a soft scrim inside a hairline frame with a plus mark on each corner, its Page history row locked. The dialog lists what Pro adds over Free in three rows. For an owner it adds the pro-rated charge for the days remaining, the card it goes to beside a two-letter tile, and Upgrade now; for a member it replaces that with a Native Select of who to ask and a two-row Textarea note, and Send request. A Segment Group, Preview as, switches the role for the demo. The one Shine Button is in the footer. For preview the dialog is drawn open in place when the page loads; the Upgrade button opens the real Dialog, with the same content and the same state. For team products with seats, admins and members.
FAQ
Why is the dialog already open, and what do I keep?
So you can see it without a click. On load the dialog is a copy drawn in place over the page: a native dialog element that is not modal, so nothing is trapped, hidden or focused. The Upgrade button opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same UpgradeBody and share the block's state. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element.
What is Preview as, and where does the role come from?
It exists only so one demo can show both versions. In your app delete the Segment Group and read the role from the session: owners and billing contacts get the purchase pane, everyone else gets the request pane. The server must enforce it as well; hiding a button is not authorisation. Both panes sit in one grid cell, so the dialog keeps its height whichever one is shown.
How is the charge worked out?
In whole cents: the Pro monthly price times the days remaining in the billing period over its length, rounded half up, from the typed PERIOD_START, PERIOD_END and AS_OF. It is shown with the card brand and last four digits only. Upgrade now does not charge anything here: it unlocks the drawn row, relabels the header button to On Pro and says so in a polite status message. In your app call your billing API and show its result; collect cards in your payment provider's hosted fields, never in this dialog.
What does Send request do?
It keeps the request in local state and says "Request to Nuala Brandt kept in this demo. Nobody was emailed." The note is optional and limited to 200 characters; a longer one shows its problem in the same reserved slot as the hint, keeps the dialog open and puts focus back in the note. In your app post the person and the note to your server in submit, and let it notify the owner.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus stays inside until it closes, and Escape, Cancel and the close button return focus to Upgrade. The trigger is the Upgrade button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange. The copy shown on load moves no focus.