# UpgradeDialog

> The in-app ways to ask for an upgrade — a promotion popup with plans and an offer countdown, a banner, a sidebar card, a feature gate and a Pro badge.

Source: https://docs.nasaqui.com/components/upgrade-prompt

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/upgrade-prompt.json
```

The places inside an app where people decide to pay. Each one leads with what they get, shows the price, and goes straight to checkout:

- `UpgradeDialog`: the promotion popup. A brand hero, three to five benefits, a Monthly / Yearly switch, one price or a [`PlanPicker`](https://docs.nasaqui.com/components/pricing-table), an optional time-limited offer with a live countdown, and one button.
- `UpgradeBanner`: a strip at the top of a page: trial ending, limit near, offer.
- `UpgradeCard`: the nudge in the sidebar footer, with an optional usage bar. Shrinks to an icon when the sidebar is collapsed.
- `FeatureGate`: wraps a paid feature. Locked, it shows a blurred preview with an upgrade panel on top.
- `PlanBadge`: a small "Pro" mark on menu items, settings and buttons.

## When to use

- Someone hits a limit or clicks a paid feature: open `UpgradeDialog`.
- A trial is ending or usage is near the limit: `UpgradeBanner` (tone `warning`).
- A standing reminder while on a free plan: `UpgradeCard` in `SidebarFooter`.
- A page or panel that only paid plans can use: `FeatureGate`.

## When not to use

- The full pricing page: use [`PricingTable`](https://docs.nasaqui.com/components/pricing-table).
- A blocking error unrelated to plans: use [`AlertDialog`](https://docs.nasaqui.com/components/alert-dialog).
- More than one prompt on screen at a time. Pick one.

## Import

```tsx
import { UpgradeDialog, UpgradeBanner, UpgradeCard, FeatureGate, PlanBadge } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { Button, UpgradeDialog, type PricingPlan } from "@fadymondy/nasaq/web";

const pro: PricingPlan = { id: "pro", name: "Pro", monthly: 15, yearly: 12 };

export function ProjectLimit() {
  const [open, setOpen] = useState(false);
  return (
    <UpgradeDialog
      open={open}
      onOpenChange={setOpen}
      trigger={<Button>New project</Button>}
      title="Unlock unlimited projects"
      description="You've used all 3 projects on Free."
      benefits={["Unlimited projects", "Client portal", "Priority support"]}
      plans={[pro]}
      offer={{ label: "Launch offer: 30% off your first year", endsAt: "2026-10-08T00:00:00Z" }}
      onUpgrade={async (planId, period) => location.assign(await checkoutUrl(planId, period))}
    />
  );
}
```

## Anatomy

```
UpgradeDialog                 data-slot="upgrade-dialog"   Dialog
├─ hero                       brand-tinted: icon, DialogTitle, DialogDescription
├─ benefits                   <ul>, success checks
├─ BillingPeriodSwitch        when a plan has a yearly price
├─ Price | PlanPicker         one plan: its price; several: radio cards
├─ offer                      accent box, label + live countdown (role="timer")
├─ upgrade button             primary, aria-busy while onUpgrade's promise runs
├─ "Maybe later"              DialogClose
└─ note                       shield icon, "Cancel anytime…"

UpgradeBanner                 data-slot="upgrade-banner", data-tone   role="region"
UpgradeCard                   data-slot="upgrade-card"   card, or an icon button when the sidebar is collapsed
FeatureGate                   data-slot="feature-gate", data-locked
├─ preview                    children, blurred, inert, aria-hidden
└─ panel                      lock, PlanBadge, title, description, button
PlanBadge                     data-slot="plan-badge"   brand Badge + sparkles
```

## API

### `UpgradeDialog`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open?` / `defaultOpen?` / `onOpenChange?` | | | Controlled or uncontrolled open state. |
| `trigger?` | `ReactElement` | none | Becomes the dialog trigger, usually a `<Button />`. |
| `icon?` | `ReactNode` | sparkles | The hero icon. |
| `title` | `ReactNode` | required | What they get: "Unlock unlimited projects". |
| `description?` | `ReactNode` | none | Why now: "You've used all 3 projects on Free." |
| `benefits?` | `ReactNode[]` | none | Three to five concrete wins. |
| `plans?` | `PricingPlan[]` | none | One plan shows its price; several show a `PlanPicker`. The current plan is left out. |
| `defaultPlanId?` | `string` | highlighted, else first | |
| `currentPlanId?` | `string` | none | Removed from the choices. |
| `currency?` | `string` | `"USD"` | |
| `defaultPeriod?` | `BillingPeriod` | `"year"` | Starts on yearly when a yearly price exists. |
| `offer?` | `{ label, endsAt? }` | none | A time-limited offer with a live countdown. |
| `note?` | `ReactNode \| null` | "Cancel anytime. Your data stays yours." | `null` hides it. |
| `onUpgrade` | `(planId, period) => void \| Promise` | required | Return a promise to keep the button busy until checkout opens. |
| `cta?` | `ReactNode` | "Upgrade to {plan}" | |
| `labels?` | `UpgradeLabels` | locale | |

### `UpgradeBanner`

`ComponentProps<"div">` minus `title`, plus:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `tone?` | `"brand" \| "warning"` | `"brand"` | `warning` for a limit near or a trial ending. |
| `icon?` | `ReactNode` | sparkles | |
| `title` | `ReactNode` | required | |
| `description?` | `ReactNode` | none | |
| `action?` | `ReactNode` | none | Usually a `Button`. |
| `onDismiss?` | `() => void` | none | Shows a dismiss button. Only for offers. |

### `UpgradeCard`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `ReactNode` | required | Also the tooltip when collapsed. |
| `description?` | `ReactNode` | none | |
| `usage?` | `{ value, max, label? }` | none | A `Meter` that turns warning and danger as it fills. |
| `actionLabel?` | `ReactNode` | "Upgrade" | |
| `onUpgrade` | `() => void` | required | Usually opens `UpgradeDialog`. |

### `FeatureGate`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `locked` | `boolean` | required | `false` renders the children untouched. |
| `title` | `ReactNode` | required | "Custom reports are on Pro". |
| `description?` | `ReactNode` | none | |
| `plan?` | `ReactNode` | "Pro" | The plan in the badge. |
| `actionLabel?` | `ReactNode` | "See plans" | |
| `onUpgrade` | `() => void` | required | |

### `PlanBadge`

`Badge` props. `children` is the plan name, default "Pro".

## Examples

### Several plans in the popup

```tsx
<UpgradeDialog
  defaultOpen
  title="Choose your plan"
  plans={plans}
  currentPlanId="free"
  onUpgrade={(planId, period) => checkout(planId, period)}
/>
```

### Trial banner

```tsx
<UpgradeBanner
  tone="warning"
  title="Your trial ends in 3 days"
  description="Pick a plan to keep your projects and history."
  action={<Button size="sm">Choose a plan</Button>}
/>
```

### Sidebar card

```tsx
<SidebarFooter>
  <UpgradeCard title="You're on Free" usage={{ value: 8, max: 10, label: "8 of 10 projects" }} onUpgrade={() => setUpgradeOpen(true)} />
  <UserMenu … />
</SidebarFooter>
```

### Gated feature

```tsx
<FeatureGate locked={plan === "free"} title="Custom reports are on Pro" description="Build and schedule your own reports." onUpgrade={() => setUpgradeOpen(true)}>
  <ReportsPreview />
</FeatureGate>
```

## Accessibility

- `UpgradeDialog` is a modal `Dialog`: focus moves in, `Esc` closes, the title and description name it. "Maybe later" is a real close button, as easy to reach as the upgrade button.
- The offer countdown is `role="timer"` with `aria-live="off"`, so it does not chatter every second.
- `UpgradeBanner` is a named region. `UpgradeCard` collapsed is an icon button with an `aria-label` and a tooltip.
- `FeatureGate` makes the preview `inert` and `aria-hidden`: nobody can tab into or use the locked feature, and screen readers hear only the upgrade panel.

| Key | Action |
| --- | --- |
| `Tab` | Moves through the dialog: switch, plans, upgrade, Maybe later. |
| `Esc` | Closes the dialog. |

## RTL & i18n

- Built-in strings in English and Arabic follow the Nasaq locale: Pro, Upgrade, Upgrade now, Upgrade to {plan}, Maybe later, Dismiss, Cancel anytime…, Ends in, See plans. Override with `labels`.
- The countdown uses Latin digits in an LTR island so it reads the same in both directions.
- The collapsed `UpgradeCard` tooltip opens toward the content side.

## Styling & tokens

- Brand tints are `color-mix` of `--nq-brand` into `--nq-surface` (10–12%); solid marks use `bg-primary text-primary-foreground`. Warning uses `nq-warning`, `nq-warning-soft`, `nq-warning-text`; offers use `nq-accent`; benefit checks use `nq-success-soft`.
- Target `[data-slot=upgrade-dialog]`, `[data-slot=upgrade-banner][data-tone]`, `[data-slot=upgrade-card]`, `[data-slot=feature-gate]`, `[data-slot=plan-badge]`.

## Do / Don't

- **Do** open the dialog at the moment of need (the limit, the locked click), not on page load.
- **Do** say what they get in the title; put the price below it.
- **Do** always offer "Maybe later". Never trap people in an upgrade dialog.
- **Do** use a real `endsAt` for offers. Don't invent fake urgency with a countdown that resets.
- **Don't** make `UpgradeBanner` dismissible when the limit is actually hit; the banner explains why things stopped.
- **Don't** show more than one upgrade prompt at a time.

## Related

- [PricingTable](https://docs.nasaqui.com/components/pricing-table) · [PlanCard](https://docs.nasaqui.com/components/plan-card) · [Dialog](https://docs.nasaqui.com/components/dialog) · [UsageMeter](https://docs.nasaqui.com/components/usage-meter) · [AppShell](https://docs.nasaqui.com/components/app-shell)

## Lab

https://docs.nasaqui.com/?path=/docs/components-pricing-upgrade-prompt--docs

## Code

### React

```tsx
import { Button, UpgradeDialog, type PricingPlan } from "@fadymondy/nasaq/web";

const pro: PricingPlan = { id: "pro", name: "Pro", monthly: 15, yearly: 12 };

export function ProjectLimit() {
  const [open, setOpen] = useState(false);
  return (
    <UpgradeDialog
      open={open}
      onOpenChange={setOpen}
      trigger={<Button>New project</Button>}
      title="Unlock unlimited projects"
      description="You've used all 3 projects on Free."
      benefits={["Unlimited projects", "Client portal", "Priority support"]}
      plans={[pro]}
      offer={{ label: "Launch offer: 30% off your first year", endsAt: "2026-10-08T00:00:00Z" }}
      onUpgrade={async (planId, period) => location.assign(await checkoutUrl(planId, period))}
    />
  );
}
```

### shadcn

```tsx
import { Button } from "@/components/ui/button";
import { UpgradeDialog } from "@/components/ui/upgrade-prompt";
import { type PricingPlan } from "@/components/ui/pricing-table";

const pro: PricingPlan = { id: "pro", name: "Pro", monthly: 15, yearly: 12 };

export function ProjectLimit() {
  const [open, setOpen] = useState(false);
  return (
    <UpgradeDialog
      open={open}
      onOpenChange={setOpen}
      trigger={<Button>New project</Button>}
      title="Unlock unlimited projects"
      description="You've used all 3 projects on Free."
      benefits={["Unlimited projects", "Client portal", "Priority support"]}
      plans={[pro]}
      offer={{ label: "Launch offer: 30% off your first year", endsAt: "2026-10-08T00:00:00Z" }}
      onUpgrade={async (planId, period) => location.assign(await checkoutUrl(planId, period))}
    />
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqButton, NqFeatureGate, NqUpgradeBanner, NqUpgradeDialog, type PricingPlan } from "@fadymondy/nasaq/vue";
import { ref } from "vue";

const plans: PricingPlan[] = [
  { id: "team", name: "Team", monthly: 15, yearly: 12, highlighted: true, description: "Everything your team needs" },
  { id: "business", name: "Business", monthly: 40, yearly: 32, description: "Advanced controls" },
];
const open = ref(false);
function upgrade(planId: string | undefined, period: string) {
  console.log("checkout", planId, period);
  open.value = false;
}
</script>

<template>
  <div class="flex flex-col gap-4">
    <NqUpgradeBanner title="Your trial ends in 3 days" description="Pick a plan to keep your projects." tone="warning">
      <template #action><NqButton size="sm" variant="primary" @click="open = true">Choose a plan</NqButton></template>
    </NqUpgradeBanner>
    <NqFeatureGate locked title="Custom reports are on Team" :on-upgrade="() => (open = true)">
      <div class="h-24 rounded-card bg-muted p-4">Revenue by month</div>
    </NqFeatureGate>
    <NqUpgradeDialog
      v-model:open="open"
      title="Unlock unlimited projects"
      description="You have used all 3 projects on the free plan."
      :benefits="['Unlimited projects', 'Priority support', 'Advanced reports']"
      :plans="plans"
      :on-upgrade="upgrade"
    />
  </div>
</template>
```

### Blade

```blade
@php
    $plans = [
        ['id' => 'team', 'name' => 'Team', 'monthly' => 15, 'yearly' => 12, 'highlighted' => true, 'description' => 'Everything your team needs'],
        ['id' => 'business', 'name' => 'Business', 'monthly' => 40, 'yearly' => 32, 'description' => 'Advanced controls'],
    ];
@endphp
<div class="flex flex-col gap-4">
    <x-nq::upgrade-prompt.banner tone="warning" dismissible title="Your trial ends in 3 days" description="Pick a plan to keep your projects.">
        <x-slot:action><x-nq::button size="sm" variant="primary">Choose a plan</x-nq::button></x-slot:action>
    </x-nq::upgrade-prompt.banner>
    <x-nq::upgrade-prompt.feature-gate :locked="true" title="Custom reports are on Team">
        <div class="h-24 rounded-card bg-muted p-4">Revenue by month</div>
    </x-nq::upgrade-prompt.feature-gate>
    <x-nq::upgrade-prompt title="Unlock unlimited projects" description="You have used all 3 projects on the free plan." :benefits="['Unlimited projects', 'Priority support', 'Advanced reports']" :plans="$plans">
        <x-nq::upgrade-prompt.trigger variant="primary">Upgrade</x-nq::upgrade-prompt.trigger>
    </x-nq::upgrade-prompt>
</div>
```

### HTML + Alpine

```html
<div class="flex flex-col gap-4">
    <div data-slot="upgrade-banner" data-tone="warning" role="region"  aria-label="Your trial ends in 3 days"      x-data="nqUpgradeBanner()" x-modelable="open" x-show="open"     class="@container flex items-center gap-3 rounded-card px-4 py-3 bg-nq-warning-soft ring-1 ring-nq-warning/30">
    <span class="grid size-8 shrink-0 place-items-center rounded-full [&amp;_svg]:size-4 bg-nq-surface text-nq-warning-text ring-1 ring-nq-warning/40">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
  <path d="M20 2v4"/>
  <path d="M22 4h-4"/>
  <circle cx="4" cy="20" r="2"/>
</svg>    </span>
    <div class="flex min-w-0 flex-1 flex-col gap-2 @xl:flex-row @xl:items-center @xl:gap-4">
        <div class="min-w-0 flex-1">
            <p class="text-label text-nq-warning-text">Your trial ends in 3 days</p>
            <p class="text-body-sm text-muted-foreground">Pick a plan to keep your projects.</p>        </div>
        <div class="shrink-0"><button data-slot="button"
     type="button"                         class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control-sm px-2.5">
        Choose a plan</button></div>    </div>
            <button data-slot="button"
     type="button"                         aria-label="Dismiss" x-on:click="dismiss()" class="inline-flex select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover p-0 size-7 shrink-0 self-start">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
    </div>
    <div data-slot="feature-gate" data-locked="" x-data class="relative isolate overflow-hidden rounded-card">
    <div aria-hidden="true" inert class="pointer-events-none select-none blur-[3px] saturate-50"><div class="h-24 rounded-card bg-muted p-4">Revenue by month</div></div>
    <div class="absolute inset-0 grid place-items-center bg-background/55 p-4">
        <div class="flex max-w-sm flex-col items-center gap-3 rounded-card bg-card p-5 text-center shadow-lg ring-1 ring-border">
            <span class="grid size-10 place-items-center rounded-full bg-secondary text-muted-foreground"><svg aria-hidden="true" class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="18" height="11" x="3" y="11" rx="2" ry="2"/>
  <path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg></span>
            <span data-slot="plan-badge"     class="inline-flex h-5 shrink-0 items-center whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-brand/40 bg-[color-mix(in_oklab,var(--nq-brand)_14%,transparent)] text-foreground gap-1"><svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
  <path d="M20 2v4"/>
  <path d="M22 4h-4"/>
  <circle cx="4" cy="20" r="2"/>
</svg>    Pro</span>
            <p class="text-h4 text-foreground">Custom reports are on Team</p>
                        <button data-slot="button"
     type="button"                         x-on:click="$dispatch(`nq-upgrade`)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        See plans</button>
        </div>
    </div>
</div>
    <div data-slot="upgrade-prompt" x-data="nqUpgradePrompt(JSON.parse('{\u0022open\u0022:false,\u0022planId\u0022:\u0022team\u0022,\u0022period\u0022:\u0022year\u0022,\u0022names\u0022:{\u0022team\u0022:\u0022Team\u0022,\u0022business\u0022:\u0022Business\u0022},\u0022upgradeTo\u0022:\u0022Upgrade to :name\u0022,\u0022upgradeNow\u0022:\u0022Upgrade now\u0022}'))" x-modelable="open" x-id="['nq-dialog']" class="contents">
    <button data-slot="dialog-trigger"
     type="button"                         aria-haspopup="dialog" x-on:click="show()" :aria-expanded="open" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        Upgrade</button>
    <template x-teleport="body">
        <div data-slot="dialog-portal">
            <div data-slot="dialog-backdrop" x-nq-presence="open" x-on:click="close()" class="fixed inset-0 z-50 bg-nq-fg/15 dark:bg-nq-bg/60 transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"></div>
            <div data-slot="upgrade-dialog" x-bind="popup" x-nq-presence="open" x-trap.noscroll="open"
                class="fixed inset-0 z-50 m-auto grid h-fit w-[calc(100%-2rem)] rounded-floating border border-border bg-popover text-popover-foreground outline-none max-h-[calc(100dvh-2rem)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0 max-w-md gap-0 overflow-hidden p-0">
                <div class="flex flex-col items-center gap-3 bg-[color-mix(in_oklab,var(--nq-brand)_12%,var(--nq-surface))] px-6 pt-8 pb-6 text-center">
                    <span class="grid size-12 place-items-center rounded-full bg-primary text-primary-foreground shadow-md [&_svg]:size-6"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
  <path d="M20 2v4"/>
  <path d="M22 4h-4"/>
  <circle cx="4" cy="20" r="2"/>
</svg></span>
                    <h2 data-slot="dialog-title" :id="$id('nq-dialog', 'title')" class="text-h3 text-foreground">Unlock unlimited projects</h2>
                    <p data-slot="dialog-description" :id="$id('nq-dialog', 'description')" class="text-body-sm text-muted-foreground">You have used all 3 projects on the free plan.</p>
                </div>
                <div class="flex flex-col gap-5 p-6">
                                            <ul class="flex flex-col gap-2.5">
                                                            <li class="flex items-start gap-2.5 text-body-sm text-foreground">
                                    <span class="mt-0.5 grid size-4 shrink-0 place-items-center rounded-full bg-nq-success-soft text-nq-success-text"><svg aria-hidden="true" stroke-width="3" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
                                    Unlimited projects
                                </li>
                                                            <li class="flex items-start gap-2.5 text-body-sm text-foreground">
                                    <span class="mt-0.5 grid size-4 shrink-0 place-items-center rounded-full bg-nq-success-soft text-nq-success-text"><svg aria-hidden="true" stroke-width="3" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
                                    Priority support
                                </li>
                                                            <li class="flex items-start gap-2.5 text-body-sm text-foreground">
                                    <span class="mt-0.5 grid size-4 shrink-0 place-items-center rounded-full bg-nq-success-soft text-nq-success-text"><svg aria-hidden="true" stroke-width="3" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
                                    Advanced reports
                                </li>
                                                    </ul>
                                            <div class="flex flex-col gap-3">
                                                            <div class="flex justify-center">
                                    <div data-slot="billing-period-switch" class="flex items-center gap-2">
    <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022year\u0022]'), false)" x-modelable="value" x-bind="root"
            aria-label="Billing period" x-model="sel" class="flex w-fit max-w-full gap-0.5 rounded-control bg-secondary p-0.5">
    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('month')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs px-3">Monthly</button>
        <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('year')"      data-pressed         class="inline-flex h-7 shrink-0 items-center justify-center whitespace-nowrap text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs gap-2 px-3">Yearly
            <span data-slot="badge"     class="inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border text-caption font-medium [&_svg]:size-3 border-nq-accent/40 bg-nq-accent/15 text-nq-accent-text h-5 px-1.5">Save 20%</span></button>
</div>
</div>
                                </div>
                                                                                        <div role="radiogroup" data-slot="plan-picker" aria-orientation="vertical" x-data="nqRadioGroup('team')" x-modelable="value" x-bind="root"
                x-model="planId" class="flex flex-col gap-2">
    <button type="button" role="radio" data-slot="radio-card" x-bind="radio('team')"
    aria-checked="true" tabindex="0"
     data-checked         class="group/card relative flex w-full cursor-pointer items-start gap-3 rounded-card border border-border bg-card p-4 text-start outline-none transition-colors duration-150 ease-nq hover:border-nq-line-strong data-checked:border-primary data-checked:bg-nq-selected focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50">
    <span data-slot="radio-card-mark" aria-hidden="true"
        class="mt-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-full border border-nq-line-strong bg-card group-data-checked/card:border-primary group-data-checked/card:bg-primary">
        <span class="block size-1.5 rounded-full bg-primary-foreground" x-show="isChecked('team')" ></span>
    </span>
    <span class="flex min-w-0 flex-1 flex-col gap-0.5">
        <span data-slot="radio-card-title" class="text-label text-foreground"><span class="flex flex-wrap items-center gap-2">
                                                Team
                                                                                            </span></span>
        <span data-slot="radio-card-description" class="text-caption text-muted-foreground">Everything your team needs</span>    </span>
    <span data-slot="radio-card-meta" class="shrink-0 text-label text-foreground"><span class="contents" x-show="period === 'month'"  style="display: none" >            <span data-slot="price" class="inline-flex flex-wrap items-baseline gap-x-1.5 text-foreground text-body-sm">
        <span>
            <bdi class="tabular-nums font-medium">$15</bdi>
            <span class="text-muted-foreground">/mo</span>        </span>
            </span>
        </span>        <span class="contents" x-show="period === 'year'" >            <span data-slot="price" class="inline-flex flex-wrap items-baseline gap-x-1.5 text-foreground text-body-sm">
        <span>
            <bdi class="tabular-nums font-medium">$12</bdi>
            <span class="text-muted-foreground">/mo</span>        </span>
                    <s class="text-caption text-muted-foreground decoration-muted-foreground/60">
                <span class="sr-only">was </span>
                <bdi class="tabular-nums">$15</bdi>
            </s>
            </span>
        </span></span></button>
                                                                                                                    <button type="button" role="radio" data-slot="radio-card" x-bind="radio('business')"
    aria-checked="false" tabindex="-1"
     data-unchecked         class="group/card relative flex w-full cursor-pointer items-start gap-3 rounded-card border border-border bg-card p-4 text-start outline-none transition-colors duration-150 ease-nq hover:border-nq-line-strong data-checked:border-primary data-checked:bg-nq-selected focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50">
    <span data-slot="radio-card-mark" aria-hidden="true"
        class="mt-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-full border border-nq-line-strong bg-card group-data-checked/card:border-primary group-data-checked/card:bg-primary">
        <span class="block size-1.5 rounded-full bg-primary-foreground" x-show="isChecked('business')"  style="display: none" ></span>
    </span>
    <span class="flex min-w-0 flex-1 flex-col gap-0.5">
        <span data-slot="radio-card-title" class="text-label text-foreground"><span class="flex flex-wrap items-center gap-2">
                                                Business
                                                                                            </span></span>
        <span data-slot="radio-card-description" class="text-caption text-muted-foreground">Advanced controls</span>    </span>
    <span data-slot="radio-card-meta" class="shrink-0 text-label text-foreground"><span class="contents" x-show="period === 'month'"  style="display: none" >            <span data-slot="price" class="inline-flex flex-wrap items-baseline gap-x-1.5 text-foreground text-body-sm">
        <span>
            <bdi class="tabular-nums font-medium">$40</bdi>
            <span class="text-muted-foreground">/mo</span>        </span>
            </span>
        </span>        <span class="contents" x-show="period === 'year'" >            <span data-slot="price" class="inline-flex flex-wrap items-baseline gap-x-1.5 text-foreground text-body-sm">
        <span>
            <bdi class="tabular-nums font-medium">$32</bdi>
            <span class="text-muted-foreground">/mo</span>        </span>
                    <s class="text-caption text-muted-foreground decoration-muted-foreground/60">
                <span class="sr-only">was </span>
                <bdi class="tabular-nums">$40</bdi>
            </s>
            </span>
        </span></span></button>
    </div>
                                                    </div>
                    <div class="flex flex-col gap-2">
                        <button data-slot="button"
     type="button"                         x-on:click="upgrade()" x-bind:aria-busy="pending ? &#039;true&#039; : null" x-bind:disabled="pending" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-[calc(var(--nq-control)+8px)] px-5 text-body">
        <span x-text="ctaLabel">Upgrade to Team</span></button>
                        <button data-slot="dialog-close"
     type="button"                         x-on:click="close()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Maybe later</button>
                    </div>
                                            <p class="flex items-center justify-center gap-1.5 text-center text-caption text-muted-foreground">
                            <svg aria-hidden="true" class="size-3.5 shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>
  <path d="m9 12 2 2 4-4"/>
</svg>                            Cancel anytime. Your data stays yours.
                        </p>
                                    </div>
                <button type="button" data-slot="dialog-close" x-on:click="close()" aria-label="Close"
                    class="absolute end-3 top-3 inline-flex size-8 items-center justify-center rounded-control text-muted-foreground transition-colors duration-150 hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
            </div>
        </div>
    </template>
</div>
</div>
```
