# PricingTable

> Plans from data with a Monthly / Yearly switch and working subscribe, upgrade and downgrade buttons, plus a feature comparison matrix and a compact plan picker.

Source: https://docs.nasaqui.com/components/pricing-table

## Install

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

Everything a pricing page needs to turn a visitor into a subscriber, built from one list of plans:

- `PricingTable`: a Monthly / Yearly switch (with the yearly saving on a badge) and one [`PlanCard`](https://docs.nasaqui.com/components/plan-card) per plan. Each card has a working button that knows the account's current plan: "Start 14-day free trial", "Upgrade · Team", "Downgrade", "Current plan", "Contact sales".
- `PlanComparison`: every feature, plan by plan, with a sticky header holding each plan's name, price and button.
- `PlanPicker`: the same plans as compact radio cards, for an upgrade dialog, checkout or onboarding step.
- `BillingPeriodSwitch`: the Monthly / Yearly switch on its own.

## When to use

- A public pricing page: `PricingTable`, then `PlanComparison` under it.
- An in-app "Plans" or "Billing" page: pass `currentPlanId` so the buttons say Upgrade or Downgrade.
- Choosing a plan inside a dialog or checkout: `PlanPicker`.

## When not to use

- An upgrade popup at the moment someone hits a limit: use [`UpgradeDialog`](https://docs.nasaqui.com/components/upgrade-prompt) (it uses `PlanPicker` inside).
- Hand-built cards with custom content: use [`PlanCard`](https://docs.nasaqui.com/components/plan-card) and `PlanGrid` directly.
- Selling one-off products: use [`ProductCard`](https://docs.nasaqui.com/components/product-card).

## Import

```tsx
import { PricingTable, PlanComparison, PlanPicker, BillingPeriodSwitch, type PricingPlan } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

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

const plans: PricingPlan[] = [
  { id: "free", name: "Free", description: "For trying it out.", monthly: 0, features: ["3 projects", "Community support"] },
  {
    id: "pro",
    name: "Pro",
    description: "For growing teams.",
    monthly: 15,
    yearly: 12, // per month, billed yearly
    highlighted: true,
    badge: "Most popular",
    trialDays: 14,
    featuresTitle: "Everything in Free, plus",
    features: ["Unlimited projects", "Priority support"],
  },
  { id: "enterprise", name: "Enterprise", custom: "Custom", features: ["SSO", "SLA"] },
];

export function Pricing() {
  return (
    <PricingTable
      plans={plans}
      onSelect={async (plan, period) => {
        const { url } = await createCheckout(plan.id, period); // your API
        location.assign(url);
      }}
      note="Prices in USD, excluding VAT. Cancel anytime."
    />
  );
}
```

## Anatomy

```
PricingTable                  data-slot="pricing-table"
├─ BillingPeriodSwitch        data-slot="billing-period-switch"   ToggleGroup + "Save N%" badge
├─ PlanGrid > PlanCard × n    see plan-card
│  └─ Button                  label and variant from planAction(); aria-busy while onSelect's promise runs
└─ note

PlanComparison                data-slot="plan-comparison"   <table>, scrolls sideways when narrow
├─ thead (sticky)             plan name, price, optional button per column
└─ tbody                      section heading rows (<th scope="colgroup">) and feature rows (<th scope="row">)

PlanPicker                    data-slot="plan-picker"   RadioGroup > RadioCard × n
```

## API

### `PricingPlan`

| Field | Type | Description |
| --- | --- | --- |
| `id` | `string` | Stable id; passed back to you. |
| `name` | `ReactNode` | "Pro". A string is also used in button labels ("Upgrade · Pro"). |
| `description?` | `ReactNode` | Who it is for. |
| `monthly?` | `number` | Price per month, billed monthly. `0` is free. |
| `yearly?` | `number` | Price per month when billed yearly (120 a year is `10`). Shown with the monthly price struck through and "Billed $120 yearly". |
| `perSeat?` | `boolean` | The period reads per seat per month. |
| `custom?` | `ReactNode` | Shown instead of a price ("Custom"); the button becomes "Contact sales". |
| `features?` | `(ReactNode \| PlanFeature)[]` | See [`PlanFeature`](https://docs.nasaqui.com/components/plan-card). |
| `featuresTitle?` | `ReactNode` | "Everything in Free, plus". |
| `badge?` | `ReactNode` | "Most popular". |
| `highlighted?` | `boolean` | The recommended plan. At most one. Its button is primary. |
| `trialDays?` | `number` | The button reads "Start 14-day free trial". |
| `cta?` | `ReactNode` | Overrides the button label. |
| `footnote?` | `ReactNode` | Fine print under the button. |

Plans are listed smallest first; the order decides Upgrade versus Downgrade.

### `PricingTable`

`PricingTableProps extends Omit<ComponentProps<"div">, "onSelect">`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `plans` | `PricingPlan[]` | required | Smallest first. Up to four. |
| `currency?` | `string` | `"USD"` | ISO 4217 code. |
| `period?` / `defaultPeriod?` | `BillingPeriod` | `"month"` | Controlled or initial billing period. |
| `onPeriodChange?` | `(period) => void` | none | The switch changed. |
| `currentPlanId?` | `string` | none | The account's plan: its card says "Current plan", others Upgrade or Downgrade. |
| `onSelect?` | `(plan, period) => void \| Promise` | none | A plan's button. Return a promise to keep the button busy (and the others disabled) until it settles. |
| `hidePeriodSwitch?` | `boolean` | `false` | Hides the switch. It is also hidden when no plan has a `yearly` price. |
| `note?` | `ReactNode` | none | One line under the plans. |
| `labels?` | `Partial<PricingLabels>` | locale | Override any built-in string. |

### `BillingPeriodSwitch`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `BillingPeriod` | required | `"month"` or `"year"`. |
| `onValueChange` | `(period) => void` | required | |
| `savings?` | `number` | `0` | Yearly saving in percent, usually `yearlySavings(plans)`. `0` hides the badge. |
| `labels?` | `Partial<PricingLabels>` | locale | |

### `PlanComparison`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `plans` | `PricingPlan[]` | required | The columns. |
| `sections` | `PlanComparisonSection[]` | required | `{ title?, rows: { label, hint?, values: Record<planId, boolean \| ReactNode> }[] }`. `true` is a check, `false` or missing a dash, anything else is shown as text ("10 GB"). |
| `currency?` / `period?` | | `"USD"` / `"month"` | For the header prices. Pass the same `period` as the `PricingTable` above it. |
| `currentPlanId?` | `string` | none | |
| `onSelect?` | `(plan, period) => void` | none | Adds each plan's button to the sticky header. |
| `caption?` | `ReactNode` | none | The table's accessible name (visually hidden). |

### `PlanPicker`

`RadioGroup` props, plus:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `plans` | `PricingPlan[]` | required | |
| `value?` / `defaultValue?` | `string` | none | The chosen plan id. |
| `onValueChange?` | `(planId) => void` | none | |
| `currency?` / `period?` | | `"USD"` / `"month"` | |
| `currentPlanId?` | `string` | none | Shown with a "Current plan" badge and disabled. |

### Helpers

- `planPrice(plan, period)`: `{ amount, compareAt? }` per month, or `null`.
- `yearlySavings(plans)`: the best yearly saving, whole percent.
- `planAction(plan, plans, labels, currentPlanId?)`: `{ label, variant, disabled }` for a plan's button, for building your own cards.
- `usePricingLabels(labels?)`: the locale's strings merged with overrides.

## Examples

### In-app plans page

```tsx
<PricingTable plans={plans} currentPlanId="free" defaultPeriod="year" onSelect={(plan, period) => openCheckout(plan.id, period)} />
```

### Pricing page with a comparison under it

```tsx
const [period, setPeriod] = useState<BillingPeriod>("year");

<PricingTable plans={plans} period={period} onPeriodChange={setPeriod} onSelect={subscribe} />
<h2>Compare plans</h2>
<PlanComparison
  plans={plans}
  period={period}
  onSelect={subscribe}
  caption="Compare plans"
  sections={[
    { title: "Projects", rows: [{ label: "Projects", values: { free: "3", pro: "Unlimited", enterprise: "Unlimited" } }] },
    { title: "Security", rows: [{ label: "SSO", hint: "SAML and OIDC", values: { enterprise: true } }] },
  ]}
/>
```

### Arabic

```tsx
<NasaqProvider locale="ar">
  <div dir="rtl">
    <PricingTable plans={arabicPlans} currency="SAR" onSelect={subscribe} />
  </div>
</NasaqProvider>
```

## Accessibility

- The switch is a `ToggleGroup` named "Billing period"; arrow keys move between Monthly and Yearly.
- Each plan is a named `<article>` (see `PlanCard`). While `onSelect`'s promise runs the button has `aria-busy` and the other buttons are disabled, so double checkouts can't start.
- `PlanComparison` is a real `<table>`: plan names are column headers, features are row headers, section titles are `scope="colgroup"` headers. Checks and dashes carry hidden "Included" / "Not included" text.
- `PlanPicker` is a radio group; the current plan is disabled, not hidden.

| Key | Action |
| --- | --- |
| `Tab` | Switch, then each plan's button. |
| `←` / `→` | Monthly / Yearly inside the switch (reading direction). |
| `↑` / `↓` | Between plans in `PlanPicker`. |

## RTL & i18n

- Built-in strings in English and Arabic follow the Nasaq locale: Monthly, Yearly, Save N%, Billed … yearly, Current plan, Get started, Choose …, Upgrade, Downgrade, Start N-day free trial, Contact sales. Override any with `labels`.
- Prices go through `Price`, formatted for the locale. Plans flow in the reading direction: the first plan is on the right in RTL.
- The comparison table scrolls horizontally on narrow screens; its first column is at the inline start.

## Styling & tokens

- Inherits `PlanCard` tokens. The comparison tints the highlighted column with a 7% `--nq-brand` mix; checks use `text-nq-brand`.
- Target `[data-slot=pricing-table]`, `[data-slot=billing-period-switch]`, `[data-slot=plan-comparison]`, `[data-slot=plan-picker]`.

## Do / Don't

- **Do** show yearly prices as a monthly equivalent, with the monthly price struck through. `yearly` does this for you.
- **Do** pass `currentPlanId` inside the app so nobody is asked to buy the plan they have.
- **Do** return a promise from `onSelect` so the button shows it is working.
- **Don't** highlight more than one plan.
- **Don't** hide the price of a self-serve plan behind "Contact sales"; use `custom` only for plans you sell by talking.

## Related

- [PlanCard](https://docs.nasaqui.com/components/plan-card) · [UpgradeDialog](https://docs.nasaqui.com/components/upgrade-prompt) · [Price](https://docs.nasaqui.com/components/price) · [RadioGroup](https://docs.nasaqui.com/components/radio-group) · [ToggleGroup](https://docs.nasaqui.com/components/toggle-group)

## Lab

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

## Code

### React

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

const plans: PricingPlan[] = [
  { id: "free", name: "Free", description: "For trying it out.", monthly: 0, features: ["3 projects", "Community support"] },
  {
    id: "pro",
    name: "Pro",
    description: "For growing teams.",
    monthly: 15,
    yearly: 12, // per month, billed yearly
    highlighted: true,
    badge: "Most popular",
    trialDays: 14,
    featuresTitle: "Everything in Free, plus",
    features: ["Unlimited projects", "Priority support"],
  },
  { id: "enterprise", name: "Enterprise", custom: "Custom", features: ["SSO", "SLA"] },
];

export function Pricing() {
  return (
    <PricingTable
      plans={plans}
      onSelect={async (plan, period) => {
        const { url } = await createCheckout(plan.id, period); // your API
        location.assign(url);
      }}
      note="Prices in USD, excluding VAT. Cancel anytime."
    />
  );
}
```

### shadcn

```tsx
import { PricingTable, type PricingPlan } from "@/components/ui/pricing-table";

const plans: PricingPlan[] = [
  { id: "free", name: "Free", description: "For trying it out.", monthly: 0, features: ["3 projects", "Community support"] },
  {
    id: "pro",
    name: "Pro",
    description: "For growing teams.",
    monthly: 15,
    yearly: 12, // per month, billed yearly
    highlighted: true,
    badge: "Most popular",
    trialDays: 14,
    featuresTitle: "Everything in Free, plus",
    features: ["Unlimited projects", "Priority support"],
  },
  { id: "enterprise", name: "Enterprise", custom: "Custom", features: ["SSO", "SLA"] },
];

export function Pricing() {
  return (
    <PricingTable
      plans={plans}
      onSelect={async (plan, period) => {
        const { url } = await createCheckout(plan.id, period); // your API
        location.assign(url);
      }}
      note="Prices in USD, excluding VAT. Cancel anytime."
    />
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqPlanComparison, NqPlanPicker, NqPricingTable, type PricingPlan } from "@fadymondy/nasaq/vue";
import { ref } from "vue";

const plans: PricingPlan[] = [
  { id: "free", name: "Free", description: "For trying it out.", monthly: 0, features: ["3 projects", "Community support"] },
  {
    id: "pro",
    name: "Pro",
    description: "For growing teams.",
    monthly: 15,
    yearly: 12, // per month, billed yearly
    highlighted: true,
    badge: "Most popular",
    trialDays: 14,
    featuresTitle: "Everything in Free, plus",
    features: ["Unlimited projects", "Priority support"],
  },
  { id: "enterprise", name: "Enterprise", custom: "Custom", features: ["SSO", "SLA"] },
];

const chosen = ref("pro");
const sections = [{ title: "Projects", rows: [{ label: "Projects", values: { free: "3", pro: "Unlimited", enterprise: "Unlimited" } }, { label: "SSO", values: { enterprise: true } }] }];

async function onSelect(plan: PricingPlan, period: string) {
  await new Promise((resolve) => setTimeout(resolve, 600)); // your checkout API
  console.log("checkout", plan.id, period);
}
</script>

<template>
  <div class="flex flex-col gap-10">
    <NqPricingTable :plans="plans" :on-select="onSelect" note="Prices in USD, excluding VAT. Cancel anytime." />
    <NqPlanComparison :plans="plans" :sections="sections" caption="Plan comparison" />
    <NqPlanPicker v-model="chosen" :plans="plans" class="max-w-md" />
  </div>
</template>
```

### Blade

```blade
@php
    $plans = [
        ['id' => 'free', 'name' => 'Free', 'description' => 'For trying it out.', 'monthly' => 0, 'features' => ['3 projects', 'Community support']],
        [
            'id' => 'pro',
            'name' => 'Pro',
            'description' => 'For growing teams.',
            'monthly' => 15,
            'yearly' => 12, // per month, billed yearly
            'highlighted' => true,
            'badge' => 'Most popular',
            'trialDays' => 14,
            'featuresTitle' => 'Everything in Free, plus',
            'features' => ['Unlimited projects', 'Priority support'],
        ],
        ['id' => 'enterprise', 'name' => 'Enterprise', 'custom' => 'Custom', 'features' => ['SSO', 'SLA']],
    ];
    $sections = [['title' => 'Projects', 'rows' => [
        ['label' => 'Projects', 'values' => ['free' => '3', 'pro' => 'Unlimited', 'enterprise' => 'Unlimited']],
        ['label' => 'SSO', 'values' => ['enterprise' => true]],
    ]]];
@endphp
<div class="flex flex-col gap-10">
    <x-nq::pricing-table :plans="$plans" note="Prices in USD, excluding VAT. Cancel anytime." x-on:nq-plan-select="console.log('checkout', $event.detail.planId, $event.detail.period)" />
    <x-nq::pricing-table.plan-comparison :plans="$plans" :sections="$sections" caption="Plan comparison" />
    <x-nq::pricing-table.plan-picker :plans="$plans" default-value="pro" class="max-w-md" />
</div>
```

### HTML + Alpine

```html
<div class="flex flex-col gap-10">
    <div data-slot="pricing-table" x-data="nqPricingTable('month')" x-modelable="period" x-on:nq-plan-select="console.log(&#039;checkout&#039;, $event.detail.planId, $event.detail.period)" class="flex flex-col items-center gap-8">
            <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('[\u0022month\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="true"
     x-bind="toggle('month')"      data-pressed         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="false"
     x-bind="toggle('year')"             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 class="w-full">
        <div data-slot="plan-grid" class="@container">
    <div class="grid grid-cols-1 gap-x-4 gap-y-7 pt-3 @3xl:auto-cols-fr @3xl:grid-flow-col"><article data-slot="plan-card"   aria-labelledby="nq-plan-free"
    class="relative flex min-w-0 flex-col gap-5 rounded-card p-6 bg-nq-surface">
        <div class="flex flex-col gap-1">
        <h3 id="nq-plan-free" class="text-h3 text-foreground">Free</h3>
                    <p class="text-body-sm text-muted-foreground">For trying it out.</p>
            </div>
    <div class="flex flex-col gap-1">
        <span data-slot="price" data-free="" class="text-foreground text-body-sm">
        <span class="text-h2 font-semibold tracking-tight">Free</span>
    </span>
            </div>
            <div class="flex flex-col gap-2">
            <button data-slot="button"
     type="button"                         x-bind:disabled="isBlocked(&#039;free&#039;)" x-bind:data-disabled="isBlocked(&#039;free&#039;)" x-bind:aria-busy="pending === &#039;free&#039;" x-on:click="select(&#039;free&#039;)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border 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 border-border bg-card text-foreground hover:bg-nq-hover h-[calc(var(--nq-control)+8px)] px-5 text-body">
        Get started</button>
                    </div>
                <div class="flex flex-col gap-3 border-t border-border pt-5">
                        <ul class="flex flex-col gap-2.5">
                                    <li  data-included  class="flex items-start gap-2 text-body-sm text-foreground">
                                                    <svg aria-hidden="true" class="mt-0.5 size-4 shrink-0 text-nq-brand" 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>
                                                            3 projects
                                                                                </span>
                    </li>
                                    <li  data-included  class="flex items-start gap-2 text-body-sm text-foreground">
                                                    <svg aria-hidden="true" class="mt-0.5 size-4 shrink-0 text-nq-brand" 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>
                                                            Community support
                                                                                </span>
                    </li>
                            </ul>
        </div>
    </article>
                                            <article data-slot="plan-card"  data-highlighted   aria-labelledby="nq-plan-pro"
    class="relative flex min-w-0 flex-col gap-5 rounded-card p-6 bg-[color-mix(in_oklab,var(--nq-brand)_9%,var(--nq-surface))] shadow-lg ring-2 ring-nq-brand/50">
            <span data-slot="plan-card-badge"
            class="absolute -top-3 start-6 inline-flex h-6 items-center rounded-full border px-2.5 text-caption font-medium whitespace-nowrap border-transparent bg-primary text-primary-foreground">Most popular</span>
        <div class="flex flex-col gap-1">
        <h3 id="nq-plan-pro" class="text-h3 text-foreground">Pro</h3>
                    <p class="text-body-sm text-muted-foreground">For growing teams.</p>
            </div>
    <div class="flex flex-col gap-1">
        <span class="contents" x-show="period === 'month'" >            <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 text-h2 font-semibold tracking-tight">$15</bdi>
            <span class="text-muted-foreground">/mo</span>        </span>
            </span>
        </span>        <span class="contents" x-show="period === 'year'"  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 text-h2 font-semibold tracking-tight">$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>
                    <p class="text-caption text-muted-foreground"><span x-show="period === 'month'" >Billed monthly</span>
                            <span x-show="period === 'year'"  style="display: none" >Billed $144 yearly</span></p>
            </div>
            <div class="flex flex-col gap-2">
            <button data-slot="button"
     type="button"                         x-bind:disabled="isBlocked(&#039;pro&#039;)" x-bind:data-disabled="isBlocked(&#039;pro&#039;)" x-bind:aria-busy="pending === &#039;pro&#039;" x-on:click="select(&#039;pro&#039;)" 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">
        Start 14-day free trial</button>
                    </div>
                <div class="flex flex-col gap-3 border-t border-border pt-5">
                            <p class="text-label text-foreground">Everything in Free, plus</p>
                        <ul class="flex flex-col gap-2.5">
                                    <li  data-included  class="flex items-start gap-2 text-body-sm text-foreground">
                                                    <svg aria-hidden="true" class="mt-0.5 size-4 shrink-0 text-nq-brand" 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
                                                                                </span>
                    </li>
                                    <li  data-included  class="flex items-start gap-2 text-body-sm text-foreground">
                                                    <svg aria-hidden="true" class="mt-0.5 size-4 shrink-0 text-nq-brand" 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
                                                                                </span>
                    </li>
                            </ul>
        </div>
    </article>
                                            <article data-slot="plan-card"   aria-labelledby="nq-plan-enterprise"
    class="relative flex min-w-0 flex-col gap-5 rounded-card p-6 bg-nq-surface">
        <div class="flex flex-col gap-1">
        <h3 id="nq-plan-enterprise" class="text-h3 text-foreground">Enterprise</h3>
            </div>
    <div class="flex flex-col gap-1">
        <span class="text-foreground text-h2 font-semibold tracking-tight">Custom</span>
            </div>
            <div class="flex flex-col gap-2">
            <button data-slot="button"
     type="button"                         x-bind:disabled="isBlocked(&#039;enterprise&#039;)" x-bind:data-disabled="isBlocked(&#039;enterprise&#039;)" x-bind:aria-busy="pending === &#039;enterprise&#039;" x-on:click="select(&#039;enterprise&#039;)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border 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 border-border bg-card text-foreground hover:bg-nq-hover h-[calc(var(--nq-control)+8px)] px-5 text-body">
        Contact sales</button>
                    </div>
                <div class="flex flex-col gap-3 border-t border-border pt-5">
                        <ul class="flex flex-col gap-2.5">
                                    <li  data-included  class="flex items-start gap-2 text-body-sm text-foreground">
                                                    <svg aria-hidden="true" class="mt-0.5 size-4 shrink-0 text-nq-brand" 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>
                                                            SSO
                                                                                </span>
                    </li>
                                    <li  data-included  class="flex items-start gap-2 text-body-sm text-foreground">
                                                    <svg aria-hidden="true" class="mt-0.5 size-4 shrink-0 text-nq-brand" 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>
                                                            SLA
                                                                                </span>
                    </li>
                            </ul>
        </div>
    </article></div>
</div>
    </div>
            <p class="text-center text-caption text-muted-foreground">Prices in USD, excluding VAT. Cancel anytime.</p>
    </div>
    <div data-slot="plan-comparison"  class="w-full overflow-x-auto">
    <table class="w-full min-w-[40rem] border-separate border-spacing-0 text-start">
        <caption class="sr-only">Plan comparison</caption>        <thead class="sticky top-0 z-1 bg-background">
            <tr>
                <th scope="col" class="w-[28%] border-b border-border p-3 text-start align-bottom text-label text-muted-foreground">Feature</th>
                                                        <th scope="col"  class="border-b border-border p-3 text-center align-bottom font-normal">
                        <div class="flex flex-col items-center gap-2">
                            <span class="text-h4 text-foreground">Free</span>
                            <span data-slot="price" data-free="" class="text-foreground text-body-sm">
        <span class="font-medium">Free</span>
    </span>
                                                            </div>
                    </th>
                                                        <th scope="col"  data-highlighted="true"  class="border-b border-border p-3 text-center align-bottom font-normal bg-[color-mix(in_oklab,var(--nq-brand)_7%,transparent)]">
                        <div class="flex flex-col items-center gap-2">
                            <span class="text-h4 text-foreground">Pro</span>
                            <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>
                                                            </div>
                    </th>
                                                        <th scope="col"  class="border-b border-border p-3 text-center align-bottom font-normal">
                        <div class="flex flex-col items-center gap-2">
                            <span class="text-h4 text-foreground">Enterprise</span>
                            <span class="text-foreground font-medium">Custom</span>
                                                            </div>
                    </th>
                            </tr>
        </thead>
        <tbody>
                                                <tr><th scope="colgroup" colspan="4" class="px-3 pt-6 pb-2 text-start text-label text-foreground">Projects</th></tr>
                                                    <tr class="hover:bg-nq-hover/50">
                        <th scope="row" class="border-b border-border px-3 py-2.5 text-start text-body-sm font-normal text-foreground">
                                                            Projects
                                                    </th>
                                                                                <td class="border-b border-border px-3 py-2.5 text-center">
                                                                    <span class="text-body-sm text-foreground">3</span>
                                                            </td>
                                                                                <td class="border-b border-border px-3 py-2.5 text-center bg-[color-mix(in_oklab,var(--nq-brand)_7%,transparent)]">
                                                                    <span class="text-body-sm text-foreground">Unlimited</span>
                                                            </td>
                                                                                <td class="border-b border-border px-3 py-2.5 text-center">
                                                                    <span class="text-body-sm text-foreground">Unlimited</span>
                                                            </td>
                                            </tr>
                                    <tr class="hover:bg-nq-hover/50">
                        <th scope="row" class="border-b border-border px-3 py-2.5 text-start text-body-sm font-normal text-foreground">
                                                            SSO
                                                    </th>
                                                                                <td class="border-b border-border px-3 py-2.5 text-center">
                                                                    <svg aria-hidden="true" class="mx-auto size-4 text-muted-foreground/60" 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="M5 12h14"/>
</svg>                                    <span class="sr-only">Not included</span>
                                                            </td>
                                                                                <td class="border-b border-border px-3 py-2.5 text-center bg-[color-mix(in_oklab,var(--nq-brand)_7%,transparent)]">
                                                                    <svg aria-hidden="true" class="mx-auto size-4 text-muted-foreground/60" 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="M5 12h14"/>
</svg>                                    <span class="sr-only">Not included</span>
                                                            </td>
                                                                                <td class="border-b border-border px-3 py-2.5 text-center">
                                                                    <svg aria-hidden="true" class="mx-auto size-4 text-nq-brand" 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 class="sr-only">Included</span>
                                                            </td>
                                            </tr>
                                    </tbody>
    </table>
</div>
    <div role="radiogroup" data-slot="plan-picker" aria-orientation="vertical" x-data="nqRadioGroup('pro')" x-modelable="value" x-bind="root"
                class="flex flex-col gap-2 max-w-md">
    <button type="button" role="radio" data-slot="radio-card" x-bind="radio('free')"
    aria-checked="false" tabindex="-1"
     data-unchecked         x-bind:disabled="false" 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('free')"  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">
                Free
                            </span></span>
        <span data-slot="radio-card-description" class="text-caption text-muted-foreground">For trying it out.</span>    </span>
    <span data-slot="radio-card-meta" class="shrink-0 text-label text-foreground"><span data-slot="price" data-free="" class="text-foreground text-body-sm">
        <span class="font-medium">Free</span>
    </span>
        </span></button>
                    <button type="button" role="radio" data-slot="radio-card" x-bind="radio('pro')"
    aria-checked="true" tabindex="0"
     data-checked         x-bind:disabled="false" 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('pro')" ></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">
                Pro
                                    <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-brand/40 bg-[color-mix(in_oklab,var(--nq-brand)_14%,transparent)] text-foreground h-5 px-1.5">Most popular</span>
                            </span></span>
        <span data-slot="radio-card-description" class="text-caption text-muted-foreground">For growing teams.</span>    </span>
    <span data-slot="radio-card-meta" class="shrink-0 text-label text-foreground"><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></button>
                    <button type="button" role="radio" data-slot="radio-card" x-bind="radio('enterprise')"
    aria-checked="false" tabindex="-1"
     data-unchecked         x-bind:disabled="false" 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('enterprise')"  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">
                Enterprise
                            </span></span>
            </span>
    <span data-slot="radio-card-meta" class="shrink-0 text-label text-foreground"><span class="text-foreground font-medium">Custom</span>
        </span></button>
    </div>
</div>
```
