> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nozle.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# UpgradeModal

> An accessible confirmation dialog for a plan change, with your open state and either provider checkout or your own submit action.

`UpgradeModal` is the confirmation dialog behind [`UpgradeButton`](/sdks/react/components/upgrade-button), for when your page owns the open state or confirms a billing action with your own `onSubmit`.

**When to use**

* A plan change confirmation opened from your own button or menu.
* A confirm step that shows a quote you read with `useUpgradePreview`.
* Any confirm-then-call flow on a billing page, without `BillingProvider`, through `onSubmit`.

**Light**

![UpgradeModal with credits applied, new plan charge, amount due today and next billing date, light theme](https://mintcdn.com/nozle-d78f01d8/8jEvxrWAvqijYzbo/images/react/components/upgrade-modal/light.png?fit=max\&auto=format\&n=8jEvxrWAvqijYzbo\&q=85\&s=934f43558f713d0651d28c2360b05196)

**Dark**

![UpgradeModal with credits applied, new plan charge, amount due today and next billing date, dark theme](https://mintcdn.com/nozle-d78f01d8/8jEvxrWAvqijYzbo/images/react/components/upgrade-modal/dark.png?fit=max\&auto=format\&n=8jEvxrWAvqijYzbo\&q=85\&s=e42a0e8c2048d4dc778413643ac33512)

**Minimal**

![UpgradeModal with credits applied, new plan charge, amount due today and next billing date, minimal theme](https://mintcdn.com/nozle-d78f01d8/8jEvxrWAvqijYzbo/images/react/components/upgrade-modal/minimal.png?fit=max\&auto=format\&n=8jEvxrWAvqijYzbo\&q=85\&s=98322f8043c8d3ffa8918c5ebdcb733b)

**Neobrutal**

![UpgradeModal with credits applied, new plan charge, amount due today and next billing date, neobrutal theme](https://mintcdn.com/nozle-d78f01d8/8jEvxrWAvqijYzbo/images/react/components/upgrade-modal/neobrutal.png?fit=max\&auto=format\&n=8jEvxrWAvqijYzbo\&q=85\&s=b17026eb905b2ffdf48e2b06202e4501)

**Nozle**

![UpgradeModal with credits applied, new plan charge, amount due today and next billing date, nozle theme](https://mintcdn.com/nozle-d78f01d8/8jEvxrWAvqijYzbo/images/react/components/upgrade-modal/nozle.png?fit=max\&auto=format\&n=8jEvxrWAvqijYzbo\&q=85\&s=c34621b7823f7a13ba82551d6f6382d0)

**Nozle Dark**

![UpgradeModal with credits applied, new plan charge, amount due today and next billing date, nozleDark theme](https://mintcdn.com/nozle-d78f01d8/8jEvxrWAvqijYzbo/images/react/components/upgrade-modal/nozle-dark.png?fit=max\&auto=format\&n=8jEvxrWAvqijYzbo\&q=85\&s=23029656a354da7fdfba3e083e07987e)

## Importing UpgradeModal

```tsx theme={null}
import { UpgradeModal, type ProrationPreview } from '@nozle-js/react'
```

## Rendering UpgradeModal

Keep the open state yourself and close the dialog in its callbacks. Without `onSubmit`, confirm calls `createCheckout` on `BillingProvider`:

```tsx components/upgrade-flow.tsx theme={null}
import { useState } from 'react'

export function UpgradeFlow({ onClientSecret }: { onClientSecret: (secret: string) => void }) {
  const [open, setOpen] = useState(false)
  const close = () => setOpen(false)
  return (
    <>
      <button type="button" onClick={() => setOpen(true)}>Upgrade to Scale</button>
      <UpgradeModal
        isOpen={open}
        planCode="scale"
        onCancel={close}
        onStripeClientSecret={onClientSecret}
        onCheckoutStarted={close}
        onCompleted={close}
        onScheduled={close}
      />
    </>
  )
}
```

The dialog moves focus in on open and back on close, traps focus, and closes on Escape. It shows a busy state, an error message, and your own title and labels.

### What a confirm does

With `onSubmit`, confirm runs your action. Without it, confirm calls `createCheckout` with the plan code, return URL and the `subscriptionId`, `quoteId` and `idempotencyKey` you pass, then handles the result:

* a Stripe client secret goes to `onStripeClientSecret`, then `onCheckoutStarted` fires,
* a completed change or a captured Razorpay payment fires `onCompleted`,
* a `scheduled` result fires `onScheduled`,
* a `processing` result shows "Payment is processing. Confirmation may take a little while." in the dialog.

`onConfirm` fires after confirm finished without an error, by either path. See [checkout result types](/sdks/react/hooks#checkout-result-types).

## Data

`preview` holds the amounts in major units (dollars, not cents), with separate credit and debit:

```tsx theme={null}
const preview: ProrationPreview = {
  credit: 14.5,
  debit: 49.5,
  net: 35,
  currency: 'USD',
  nextBillingDate: '2026-11-01T00:00:00Z',
}

export function UpgradeDialog({ open, close }: { open: boolean; close: () => void }) {
  return <UpgradeModal isOpen={open} planCode="scale" preview={preview} onCancel={close} onCompleted={close} />
}
```

### Live data

The quote from [`useUpgradePreview`](/sdks/react/hooks#the-hooks) is in minor units, so show it as `children` and pass its `quote_id`:

```tsx theme={null}
import { useSubscription, useUpgradePreview } from '@nozle-js/react'

function money(minor: number | string, currency: string) {
  const format = new Intl.NumberFormat('en-US', { style: 'currency', currency })
  const digits = format.resolvedOptions().maximumFractionDigits ?? 2
  return format.format(Number(minor) / 10 ** digits)
}

export function QuotedUpgrade({ open, close }: { open: boolean; close: () => void }) {
  const subscription = useSubscription()
  const quote = useUpgradePreview(open ? 'scale' : undefined)
  return (
    <UpgradeModal
      isOpen={open}
      planCode="scale"
      subscriptionId={subscription.data?.subscription?.external_id}
      quoteId={quote.data?.quote_id}
      confirmDisabled={!quote.data}
      onCancel={close}
      onCompleted={close}
      onScheduled={close}
    >
      {quote.data && (
        <p>
          Due now: {money(quote.data.amount_due_now_cents, quote.data.currency)}. Effective{' '}
          {new Date(quote.data.effective_at).toLocaleDateString('en-US')}.
        </p>
      )}
    </UpgradeModal>
  )
}
```

## Examples

### Proration amounts

With `preview`, the dialog lists credits applied, the new plan charge, the amount due today and the next billing date.

![UpgradeModal with credits applied, new plan charge, amount due today and next billing date, light theme](https://mintcdn.com/nozle-d78f01d8/8jEvxrWAvqijYzbo/images/react/components/upgrade-modal/light.png?fit=max\&auto=format\&n=8jEvxrWAvqijYzbo\&q=85\&s=934f43558f713d0651d28c2360b05196)

```tsx theme={null}
const preview: ProrationPreview = { credit: 14.5, debit: 49.5, net: 35, currency: 'USD', nextBillingDate: '2026-11-01T00:00:00Z' }

export function UpgradeDialog({ open, close }: { open: boolean; close: () => void }) {
  return <UpgradeModal isOpen={open} planCode="scale" preview={preview} onCancel={close} onCompleted={close} />
}
```

### Your own submit action

With `onSubmit`, the dialog runs your action instead of `createCheckout` and needs no `BillingProvider`.

```tsx theme={null}
export function ConfirmSeats({ open, close }: { open: boolean; close: () => void }) {
  return (
    <UpgradeModal
      isOpen={open}
      planCode="team"
      title="Add 5 seats?"
      confirmLabel="Add seats"
      onSubmit={async () => {
        const response = await fetch('/api/billing/seats', { method: 'POST', body: JSON.stringify({ add: 5 }) })
        if (!response.ok) throw new Error('Seats could not be added')
      }}
      onConfirm={close}
      onCancel={close}
    >
      <p>Your next invoice includes 5 more seats.</p>
    </UpgradeModal>
  )
}
```

### Back button

`onBack` shows a Back button on the left of the actions, for a step that has a previous step.

```tsx theme={null}
export function ConfirmStep({ open, back, close }: { open: boolean; back: () => void; close: () => void }) {
  return <UpgradeModal isOpen={open} planCode="scale" onBack={back} onCancel={close} onCompleted={close} />
}
```

## API reference

**isOpen** `boolean` *required*

Shows the dialog. When `false` nothing renders.

**planCode** `string` *required*

The plan sent to `createCheckout`.

**onCancel** `() =\> void`

Fires when the Close button is clicked or Escape is pressed (not while busy). Close the dialog here.

**title** `string` (default: Confirm plan change)

The dialog title.

**confirmLabel** `string` (default: Confirm)

Text of the confirm button. While busy it reads "Checking subscription…".

**confirmDisabled** `boolean` (default: false)

Disables the confirm button.

**busy** `boolean` (default: false)

Marks the dialog busy from outside: buttons disabled, Escape ignored.

**preview** `ProrationPreview`

Amounts in major units: `credit` (shown as "Credits applied" only when above 0), `debit` ("New plan charge"), `net` ("Due today"), `nextBillingDate` (an ISO date) and `currency` (defaults to `USD`).

**children** `ReactNode`

Content below the amounts.

**locale** `string`

Formats the amounts and the next billing date. Without it the amounts use `en-US` and the date uses the browser's locale.

**onSubmit** `() =\> Promise\<void\>`

From React `0.10.0`. Your action for the confirm button. When set, `createCheckout` is not called and `BillingProvider` is not needed. A rejection shows its message.

**subscriptionId** `string`

Sent to `createCheckout`. Not sent when left out.

**quoteId** `string`

Sent to `createCheckout`. Not sent when left out.

**idempotencyKey** `string`

Sent to `createCheckout`. Not sent when left out.

**returnUrl** `string`

Sent to `createCheckout`. Defaults to the current URL.

**onStripeClientSecret** `(clientSecret: string) =\> void`

Fires for an embedded Stripe checkout and receives its client secret.

**onCheckoutStarted** `() =\> void`

Fires after the Stripe client secret was handed over.

**onCompleted** `() =\> void`

Fires when the change completed, or a Razorpay payment was captured.

**onScheduled** `() =\> void`

Fires for a `scheduled` result.

**onConfirm** `() =\> void`

Fires after confirm finished without an error, by either path.

**onBack** `() =\> void`

Shows a Back button on the left of the actions and fires when it is clicked.

**onError** `(error: Error) =\> void`

Fires when confirm fails and receives the error. The dialog shows the message.

**nonce** `string`

CSP nonce for the dialog's `<style>` element.

**appearance** `Appearance`

Theme, variables and element overrides. Defaults to the provider's. See [Appearance](/sdks/react/styling).

### States

| State | What shows |
| - | - |
| Closed (`isOpen={false}`) | Nothing. |
| Confirming, or `busy` | Buttons disabled, "Checking subscription…" on the confirm button, Escape ignored. |
| Payment processing | "Payment is processing. Confirmation may take a little while." |
| Confirm failed | The error message in the dialog. |
| No `onSubmit` and no `createCheckout` on the provider | "BillingProvider createCheckout callback is required for checkout". |

The dialog shows [Powered by Nozle](/sdks/react/components/powered-by-nozle), unless it is inside another component that already shows it.

### Appearance

> **Element keys**
>
> `upgradeModal` (the backdrop, also class `nozle-upgrade-modal`), `upgradeModal__dialog`, `upgradeModal__actions`.
>
> Style them with `appearance.elements`. See [Elements](/sdks/react/styling#elements).

## Related

> **[UpgradeButton](/sdks/react/components/upgrade-button)**
>
> A button that opens this dialog.

> **[PlanChangeControl](/sdks/react/components/plan-change-control)**
>
> Uses this dialog for its plan list and quote.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.