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

# Components

> Drop-in billing UI components for React

All components are exported from `@nozle-js/react` and use `--nozle-*` CSS custom properties for theming. They work with any design system -- just set the CSS variables on a parent element or `:root`.

<Info>
  Components that make API calls (`CheckoutButton`, `UpgradeButton`, `CreditTopUpButton`) must be wrapped in a `BillingPortalProvider`. Components that read subscription data (`CurrentPlan`, `CreditBalance`, `CreditHistory`, `InvoiceList`) require a `BillingProvider` ancestor.
</Info>

***

## Pricing Components

### PricingTable

Renders plan cards in a responsive grid with a monthly/annual toggle. Auto-fetches plans from the API when no `plans` prop is provided, and auto-detects the current plan when `customerId` is set.

```tsx theme={null}
import { PricingTable } from '@nozle-js/react';

// Auto-fetch plans from API, auto-detect current plan
<PricingTable
  customerId="cust_123"
  highlightPlan="pro"
  onSelect={(plan) => handleUpgrade(plan)}
/>

// Or pass plans explicitly
<PricingTable
  plans={[
    { code: 'starter', name: 'Starter', amount_cents: 2900, amount_currency: 'USD', interval: 'monthly' },
    { code: 'pro', name: 'Pro', amount_cents: 9900, amount_currency: 'USD', interval: 'monthly', description: 'Most popular' },
  ]}
  features={[
    ['10K API calls', 'Email support'],
    ['100K API calls', 'Priority support', 'Analytics'],
  ]}
  currentPlanCode="starter"
  highlightPlan="pro"
  onSelect={(plan) => console.log('Selected:', plan.code)}
  showToggle={true}
  enterpriseEmail="sales@example.com"
  className="my-pricing"
/>;
```

| Prop              | Type                          | Default | Description                                                       |
| ----------------- | ----------------------------- | ------- | ----------------------------------------------------------------- |
| `customerId`      | `string`                      | --      | Customer ID for auto-detecting current plan via API               |
| `currentPlanCode` | `string`                      | --      | Explicitly set current plan (overrides auto-detect)               |
| `plans`           | `PricingPlan[]`               | --      | Plans to display (auto-fetched from `/api/v1/plans` if omitted)   |
| `features`        | `string[][]`                  | --      | Feature lists per plan (index-matched to plans array)             |
| `onSelect`        | `(plan: PricingPlan) => void` | --      | Called when a plan's CTA is clicked                               |
| `highlightPlan`   | `string`                      | --      | Plan code to highlight as "Most Popular"                          |
| `enterpriseEmail` | `string`                      | --      | Email for enterprise plan "Contact Sales" mailto link             |
| `showToggle`      | `boolean`                     | `true`  | Show monthly/annual toggle (only renders when annual plans exist) |
| `className`       | `string`                      | --      | CSS class for the outer wrapper                                   |

#### PricingPlan type

| Field             | Type     | Required | Description                                         |
| ----------------- | -------- | -------- | --------------------------------------------------- |
| `code`            | `string` | Yes      | Unique plan identifier (matches plan code from API) |
| `name`            | `string` | Yes      | Display name                                        |
| `amount_cents`    | `number` | Yes      | Price in cents                                      |
| `amount_currency` | `string` | Yes      | Currency code (`USD`, `EUR`, `GBP`, `JPY`)          |
| `interval`        | `string` | Yes      | Billing interval (`monthly` or `yearly`)            |
| `description`     | `string` | No       | Subtitle below the plan name                        |

#### CSS Variable Theming

PricingTable uses CSS custom properties for full theming control. Component-specific variables fall back to global Nozle variables, which fall back to sensible defaults:

```css theme={null}
:root {
  /* Component-specific (highest priority) */
  --nozle-pricing-bg: #ffffff;
  --nozle-pricing-card-bg: #ffffff;
  --nozle-pricing-highlight: #6366f1;
  --nozle-pricing-border: #e5e7eb;
  --nozle-pricing-radius: 12px;

  /* Global Nozle variables (fallbacks) */
  --nozle-background: #ffffff;
  --nozle-card: #ffffff;
  --nozle-primary: #6366f1;
  --nozle-border: #e5e7eb;
  --nozle-radius: 12px;
  --nozle-foreground: #111827;
  --nozle-muted-foreground: #6b7280;
  --nozle-muted: #f3f4f6;
  --nozle-primary-foreground: #ffffff;
}
```

***

### PlanCard

Individual pricing plan card. Used internally by `PricingTable`, but can be rendered standalone for custom layouts.

```tsx theme={null}
import { PlanCard } from '@nozle-js/react';

<PlanCard
  id="pro"
  name="Pro"
  monthlyPrice={99}
  annualPrice={990}
  features={['100K API calls', 'Priority support', 'Analytics']}
  isAnnual={false}
  isCurrent={false}
  onSelect={() => handleUpgrade('pro')}
/>;
```

| Prop        | Type         | Default | Description                               |
| ----------- | ------------ | ------- | ----------------------------------------- |
| `...Plan`   | `Plan`       | --      | All fields from the Plan type (see above) |
| `isAnnual`  | `boolean`    | --      | Whether to show annual or monthly price   |
| `isCurrent` | `boolean`    | --      | Highlights card border and disables CTA   |
| `onSelect`  | `() => void` | --      | Click handler for the CTA button          |
| `children`  | `ReactNode`  | --      | Custom content below the features list    |

***

### PlanComparison

Side-by-side feature comparison table. Boolean values render as check/cross icons; strings render as text.

```tsx theme={null}
import { PlanComparison } from '@nozle-js/react';

<PlanComparison
  plans={plans}
  features={[
    { key: 'api_calls', label: 'API Calls', values: { starter: '10K', pro: '100K' } },
    { key: 'analytics', label: 'Analytics', values: { starter: false, pro: true } },
    { key: 'support', label: 'Priority Support', values: { starter: false, pro: true } },
  ]}
/>;
```

| Prop       | Type                  | Default | Description                  |
| ---------- | --------------------- | ------- | ---------------------------- |
| `plans`    | `Plan[]`              | --      | Plans used as column headers |
| `features` | `ComparisonFeature[]` | --      | Rows in the comparison table |

#### ComparisonFeature type

| Field    | Type                                | Description                                                      |
| -------- | ----------------------------------- | ---------------------------------------------------------------- |
| `key`    | `string`                            | Unique row identifier                                            |
| `label`  | `string`                            | Human-readable feature name                                      |
| `values` | `Record<string, string \| boolean>` | Map of `plan.id` to value. Booleans render as check/cross icons. |

***

### PlanBadge

Inline badge displaying the plan name with tier-based coloring.

```tsx theme={null}
import { PlanBadge } from '@nozle-js/react';

<PlanBadge plan="Pro" tier="pro" />
<PlanBadge plan="Free" tier="free" />
<PlanBadge plan="Enterprise" tier="enterprise" />
```

| Prop   | Type                                           | Default  | Description                        |
| ------ | ---------------------------------------------- | -------- | ---------------------------------- |
| `plan` | `string`                                       | --       | Text displayed inside the badge    |
| `tier` | `'free' \| 'starter' \| 'pro' \| 'enterprise'` | `'free'` | Controls background and text color |

***

## Checkout and Billing Actions

### CheckoutButton

Initiates a checkout session and routes to the appropriate payment processor. Supports Stripe (hosted redirect and embedded Elements) and Razorpay.

Must be used inside a `BillingPortalProvider`.

```tsx theme={null}
import { CheckoutButton } from '@nozle-js/react';

// Stripe hosted checkout (redirect)
<CheckoutButton
  planId="pro_monthly"
  label="Subscribe"
  onError={(err) => console.error(err)}
/>

// Stripe embedded checkout
<CheckoutButton
  planId="pro_monthly"
  onStripeClientSecret={(secret) => setClientSecret(secret)}
/>

// Razorpay checkout
<CheckoutButton
  planId="pro_monthly"
  razorpayKeyId="rzp_live_xxx"
  onSuccess={(paymentId) => console.log('Paid:', paymentId)}
/>
```

| Prop                   | Type                             | Default                   | Description                                                |
| ---------------------- | -------------------------------- | ------------------------- | ---------------------------------------------------------- |
| `planId`               | `string`                         | --                        | Plan to check out                                          |
| `label`                | `string`                         | `'Get Started'`           | Button text                                                |
| `apiBaseUrl`           | `string`                         | `'https://api.nozle.app'` | API base URL                                               |
| `className`            | `string`                         | --                        | CSS class for the button                                   |
| `style`                | `React.CSSProperties`            | --                        | Inline styles                                              |
| `onError`              | `(error: Error) => void`         | --                        | Called on checkout failure                                 |
| `razorpayKeyId`        | `string`                         | --                        | Razorpay key\_id (required for Razorpay path)              |
| `onStripeClientSecret` | `(clientSecret: string) => void` | --                        | Called with the Stripe client secret for embedded Elements |
| `onSuccess`            | `(paymentId: string) => void`    | --                        | Called with Razorpay payment ID on success                 |

***

### Checkout

Stripe Elements drop-in checkout form. Mount this after receiving a `clientSecret` from `CheckoutButton` or your own backend.

Requires `@stripe/react-stripe-js` and `@stripe/stripe-js` as peer dependencies.

```tsx theme={null}
import { Checkout } from '@nozle-js/react';

<Checkout
  clientSecret="pi_xxx_secret_xxx"
  publishableKey="pk_live_xxx"
  submitLabel="Pay now"
  onSuccess={(paymentIntentId) => router.push('/success')}
  onError={(err) => toast.error(err.message)}
  onReady={() => setLoading(false)}
/>
```

| Prop             | Type                                | Default     | Description                                        |
| ---------------- | ----------------------------------- | ----------- | -------------------------------------------------- |
| `clientSecret`   | `string`                            | --          | Stripe PaymentIntent client secret                 |
| `publishableKey` | `string`                            | --          | Stripe publishable key                             |
| `stripeAccount`  | `string`                            | --          | Stripe Connect account ID                          |
| `submitLabel`    | `string`                            | `'Pay now'` | Text on the submit button                          |
| `onSuccess`      | `(paymentIntentId: string) => void` | --          | Called when payment succeeds without redirect      |
| `onError`        | `(error: Error) => void`            | --          | Called on payment failure                          |
| `onReady`        | `() => void`                        | --          | Called when the PaymentElement becomes interactive |
| `className`      | `string`                            | --          | CSS class for the outer wrapper                    |
| `style`          | `React.CSSProperties`               | --          | Inline styles for the outer wrapper                |

The `useCheckout` hook is available inside the `Checkout` tree for programmatic control:

```tsx theme={null}
import { useCheckout } from '@nozle-js/react';

function CustomSubmit() {
  const { confirmPayment, isProcessing, error } = useCheckout();
  return <button onClick={confirmPayment} disabled={isProcessing}>Pay</button>;
}
```

***

### UpgradeButton

Opens an `UpgradeModal` with a live proration preview. Must be used inside a `BillingPortalProvider`.

```tsx theme={null}
import { UpgradeButton } from '@nozle-js/react';

<UpgradeButton
  targetPlanId="pro_monthly"
  label="Upgrade to Pro"
  onUpgraded={() => router.refresh()}
/>
```

| Prop           | Type                  | Default                   | Description                           |
| -------------- | --------------------- | ------------------------- | ------------------------------------- |
| `targetPlanId` | `string`              | --                        | Plan the customer is upgrading to     |
| `label`        | `string`              | `'Upgrade'`               | Button text                           |
| `apiBaseUrl`   | `string`              | `'https://api.nozle.app'` | API base URL                          |
| `className`    | `string`              | --                        | CSS class for the button              |
| `style`        | `React.CSSProperties` | --                        | Inline styles                         |
| `onUpgraded`   | `() => void`          | --                        | Called after the upgrade is confirmed |

***

### UpgradeModal

Modal dialog showing a proration preview before confirming a plan change. Fetches preview from `POST /api/v1/subscriptions/preview` and confirms via `POST /api/v1/subscriptions/change`.

Displays: credit from current plan, new plan charge, net amount due today, and next billing date.

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

<UpgradeModal
  isOpen={showModal}
  targetPlanId="pro_monthly"
  customerId="cus_xxx"
  apiBaseUrl="https://api.nozle.app"
  apiKey="pk_xxx"
  onConfirm={() => setShowModal(false)}
  onCancel={() => setShowModal(false)}
/>
```

| Prop           | Type         | Default                   | Description                              |
| -------------- | ------------ | ------------------------- | ---------------------------------------- |
| `isOpen`       | `boolean`    | --                        | Controls modal visibility                |
| `targetPlanId` | `string`     | --                        | Plan the customer is upgrading to        |
| `customerId`   | `string`     | --                        | Customer identifier                      |
| `apiBaseUrl`   | `string`     | `'https://api.nozle.app'` | API base URL                             |
| `apiKey`       | `string`     | `''`                      | API key for authorization                |
| `onConfirm`    | `() => void` | --                        | Called after successful upgrade          |
| `onCancel`     | `() => void` | --                        | Called when the user dismisses the modal |

#### ProrationPreview type

| Field             | Type     | Description                                        |
| ----------------- | -------- | -------------------------------------------------- |
| `credit`          | `number` | Credit from the remaining time on the current plan |
| `debit`           | `number` | Charge for the new plan                            |
| `net`             | `number` | Amount due today (`debit - credit`)                |
| `nextBillingDate` | `string` | ISO date string of the next billing cycle          |

***

### CreditTopUpButton

Opens a dialog for purchasing credits. Displays preset amounts ($10, $25, $50, $100, \$250) with an optional custom amount input.

Must be used inside a `BillingPortalProvider`.

```tsx theme={null}
import { CreditTopUpButton } from '@nozle-js/react';

<CreditTopUpButton
  label="Add Credits"
  onSuccess={(amount) => toast.success(`Added $${amount} in credits`)}
  onError={(err) => toast.error(err.message)}
/>
```

| Prop         | Type                       | Default                   | Description                      |
| ------------ | -------------------------- | ------------------------- | -------------------------------- |
| `label`      | `string`                   | `'Add Credits'`           | Button text                      |
| `apiBaseUrl` | `string`                   | `'https://api.nozle.app'` | API base URL                     |
| `className`  | `string`                   | --                        | CSS class for the button         |
| `style`      | `React.CSSProperties`      | --                        | Inline styles                    |
| `onSuccess`  | `(amount: number) => void` | --                        | Called with the purchased amount |
| `onError`    | `(error: Error) => void`   | --                        | Called on purchase failure       |

***

### CancelSubscriptionButton

Two-step cancellation flow. Step 1 shows a confirmation dialog. Step 2 presents a reason survey with five options: Too expensive, Missing features, Switching to competitor, Not using it enough, and Other (with free-text input). Submits `DELETE /api/v1/subscriptions/{id}` with the selected reason.

```tsx theme={null}
import { CancelSubscriptionButton } from '@nozle-js/react';

<CancelSubscriptionButton
  subscriptionId="sub_xxx"
  onCancelled={() => router.push('/goodbye')}
/>
```

| Prop             | Type         | Default | Description                             |
| ---------------- | ------------ | ------- | --------------------------------------- |
| `subscriptionId` | `string`     | --      | Subscription to cancel                  |
| `onCancelled`    | `() => void` | --      | Called after the cancellation completes |

The exported `CANCEL_REASONS` constant contains the five reason strings if you need them elsewhere:

```ts theme={null}
import { CANCEL_REASONS } from '@nozle-js/react';
// ["Too expensive", "Missing features", "Switching to competitor", "Not using it enough", "Other"]
```

***

## Billing Portal Components

### BillingPortalProvider

Context wrapper that provides `{ customerId, apiKey }` to all nested SDK components. Required by `CheckoutButton`, `UpgradeButton`, and `CreditTopUpButton`.

```tsx theme={null}
import { BillingPortalProvider } from '@nozle-js/react';

<BillingPortalProvider customerId="cus_xxx" apiKey="pk_xxx">
  <PricingTable plans={plans} />
  <CheckoutButton planId="pro" />
</BillingPortalProvider>
```

| Prop         | Type        | Default | Description                                        |
| ------------ | ----------- | ------- | -------------------------------------------------- |
| `customerId` | `string`    | --      | Customer identifier passed to all child components |
| `apiKey`     | `string`    | --      | Publishable API key (`pk_` prefix)                 |
| `children`   | `ReactNode` | --      | Child components                                   |

Access the context directly with the `useBillingPortal` hook:

```ts theme={null}
import { useBillingPortal } from '@nozle-js/react';

const { customerId, apiKey } = useBillingPortal();
```

***

### BillingPortal

Aggregated portal view that composes `CurrentPlan`, `UsageDashboard`, `InvoiceList`, and `CreditBalance` into a single vertical layout.

```tsx theme={null}
import { BillingPortal } from '@nozle-js/react';

<BillingPortal
  customerId="cus_xxx"
  usageFeatures={[
    { key: 'api_calls', label: 'API Calls', used: 8200, limit: 10000 },
    { key: 'storage', label: 'Storage (GB)', used: 3, limit: 10 },
  ]}
  onChangePlan={() => router.push('/plans')}
/>
```

| Prop             | Type                      | Default | Description                                       |
| ---------------- | ------------------------- | ------- | ------------------------------------------------- |
| `customerId`     | `string`                  | --      | Customer identifier                               |
| `usageFeatures`  | `UsageDashboardFeature[]` | --      | Usage data passed to the embedded UsageDashboard  |
| `usageLoading`   | `boolean`                 | `false` | Shows skeleton placeholders for the usage section |
| `onChangePlan`   | `() => void`              | --      | Called when the "Change Plan" button is clicked   |
| `subscriptionId` | `string`                  | --      | Subscription ID for the cancel button             |
| `onCancelled`    | `() => void`              | --      | Called after subscription cancellation            |

***

### CurrentPlan

Displays the customer's active subscription: plan name (with `PlanBadge`), billing interval, status, and next billing date. Fetches from `GET /api/v1/subscriptions/current`.

```tsx theme={null}
import { CurrentPlan } from '@nozle-js/react';

<CurrentPlan
  customerId="cus_xxx"
  onChangePlan={() => router.push('/plans')}
/>
```

| Prop             | Type         | Default    | Description                                                                       |
| ---------------- | ------------ | ---------- | --------------------------------------------------------------------------------- |
| `customerId`     | `string`     | --         | Customer identifier                                                               |
| `onChangePlan`   | `() => void` | --         | Callback for the "Change Plan" button. If omitted, navigates to `changePlanHref`. |
| `changePlanHref` | `string`     | `'/plans'` | Fallback URL when `onChangePlan` is not provided                                  |

#### CurrentPlanData type

| Field             | Type                                           | Description                                      |
| ----------------- | ---------------------------------------------- | ------------------------------------------------ |
| `id`              | `string`                                       | Subscription ID                                  |
| `planName`        | `string`                                       | Display name of the plan                         |
| `interval`        | `string`                                       | Billing interval (e.g., "monthly", "annual")     |
| `status`          | `string`                                       | Subscription status (e.g., "active", "canceled") |
| `nextBillingDate` | `string`                                       | ISO date of next charge                          |
| `tier`            | `'free' \| 'starter' \| 'pro' \| 'enterprise'` | Controls badge styling                           |

***

### CreditBalance

Displays the customer's current credit balance. Uses the `useCredits` hook internally.

```tsx theme={null}
import { CreditBalance } from '@nozle-js/react';

<CreditBalance customerId="cus_xxx" currency="USD" />
```

| Prop         | Type     | Default | Description                                           |
| ------------ | -------- | ------- | ----------------------------------------------------- |
| `customerId` | `string` | --      | Customer identifier                                   |
| `currency`   | `string` | `'USD'` | Currency code for formatting (e.g., `'EUR'`, `'GBP'`) |

***

### CreditHistory

Renders a table of credit transactions sorted newest-first. Shows up to 20 rows with a "View all" link when there are more.

```tsx theme={null}
import { CreditHistory } from '@nozle-js/react';

<CreditHistory
  customerId="cus_xxx"
  viewAllHref="/billing/credits"
/>
```

| Prop          | Type     | Default              | Description                                                         |
| ------------- | -------- | -------------------- | ------------------------------------------------------------------- |
| `customerId`  | `string` | --                   | Customer identifier                                                 |
| `viewAllHref` | `string` | `'/billing/credits'` | Link target for "View all" when there are more than 20 transactions |

#### CreditTransaction type

| Field         | Type     | Description                                                 |
| ------------- | -------- | ----------------------------------------------------------- |
| `id`          | `string` | Transaction ID                                              |
| `amount`      | `number` | Credit amount                                               |
| `type`        | `string` | One of `'grant'`, `'deduct'`, `'purchase'`, `'application'` |
| `description` | `string` | Human-readable description                                  |
| `createdAt`   | `string` | ISO timestamp                                               |

***

### InvoiceList

Displays customer invoices in a table with status badges and PDF download links. Fetches from `GET /api/v1/invoices`.

```tsx theme={null}
import { InvoiceList } from '@nozle-js/react';

<InvoiceList customerId="cus_xxx" />
```

| Prop         | Type     | Default | Description         |
| ------------ | -------- | ------- | ------------------- |
| `customerId` | `string` | --      | Customer identifier |

#### Invoice type

| Field      | Type                                                       | Description                      |
| ---------- | ---------------------------------------------------------- | -------------------------------- |
| `id`       | `string`                                                   | Invoice ID                       |
| `number`   | `string`                                                   | Invoice number (e.g., `INV-001`) |
| `date`     | `string`                                                   | ISO date string                  |
| `amount`   | `number`                                                   | Amount in cents                  |
| `currency` | `string`                                                   | Currency code                    |
| `status`   | `'draft' \| 'open' \| 'paid' \| 'void' \| 'uncollectible'` | Invoice status                   |
| `pdf_url`  | `string`                                                   | URL to download the invoice PDF  |

***

### PaymentMethodDisplay

Shows the customer's saved payment method (card brand, last 4 digits, expiry) with an "Update Payment Method" button.

```tsx theme={null}
import { PaymentMethodDisplay } from '@nozle-js/react';

<PaymentMethodDisplay
  paymentMethod={{
    brand: 'visa',
    last4: '4242',
    expMonth: 12,
    expYear: 2027,
  }}
  onUpdatePaymentMethod={() => setShowCheckout(true)}
/>
```

| Prop                    | Type            | Default                     | Description                                                                 |
| ----------------------- | --------------- | --------------------------- | --------------------------------------------------------------------------- |
| `paymentMethod`         | `PaymentMethod` | --                          | Card details to display. Shows "No payment method on file." when undefined. |
| `onUpdatePaymentMethod` | `() => void`    | --                          | Click handler for the update button. If omitted, navigates to `updateHref`. |
| `updateHref`            | `string`        | `'/billing/update-payment'` | Fallback URL when `onUpdatePaymentMethod` is not provided                   |

#### PaymentMethod type

| Field      | Type     | Description                                                          |
| ---------- | -------- | -------------------------------------------------------------------- |
| `last4`    | `string` | Last 4 digits of the card                                            |
| `brand`    | `string` | Card brand (visa, mastercard, amex, discover, jcb, diners, unionpay) |
| `expMonth` | `number` | Expiration month (1-12)                                              |
| `expYear`  | `number` | Expiration year (4-digit)                                            |

***

## Usage Components

### UsageMeter

Progress bar with automatic color thresholds based on usage percentage.

| Percentage | Color zone        |
| ---------- | ----------------- |
| \< 80%     | Primary (normal)  |
| 80 -- 94%  | Warning (amber)   |
| >= 95%     | Destructive (red) |

```tsx theme={null}
import { UsageMeter } from '@nozle-js/react';

<UsageMeter used={8200} limit={10000} label="API Calls" />
<UsageMeter used={9800} limit={10000} label="API Calls" showText={false} />
```

| Prop       | Type      | Default | Description                                |
| ---------- | --------- | ------- | ------------------------------------------ |
| `used`     | `number`  | --      | Current usage count                        |
| `limit`    | `number`  | --      | Maximum allowed usage                      |
| `label`    | `string`  | --      | Label shown above the progress bar         |
| `showText` | `boolean` | `true`  | Whether to display the "used / limit" text |

The `getUsageMeterColor` helper is also exported for use in custom components:

```ts theme={null}
import { getUsageMeterColor } from '@nozle-js/react';

const color = getUsageMeterColor(92); // returns the warning CSS variable
```

***

### UsageDashboard

Renders multiple `UsageMeter` bars for all tracked features. Shows skeleton placeholders when loading.

```tsx theme={null}
import { UsageDashboard } from '@nozle-js/react';

<UsageDashboard
  features={[
    { key: 'api_calls', label: 'API Calls', used: 8200, limit: 10000 },
    { key: 'storage', label: 'Storage (GB)', used: 3, limit: 10 },
    { key: 'seats', label: 'Team Seats', used: 4, limit: 5 },
  ]}
/>
```

| Prop       | Type                      | Default | Description                           |
| ---------- | ------------------------- | ------- | ------------------------------------- |
| `features` | `UsageDashboardFeature[]` | `[]`    | Array of usage features to display    |
| `loading`  | `boolean`                 | `false` | Shows skeleton placeholders when true |

#### UsageDashboardFeature type

| Field   | Type     | Description                 |
| ------- | -------- | --------------------------- |
| `key`   | `string` | Unique feature identifier   |
| `label` | `string` | Human-readable feature name |
| `used`  | `number` | Current usage count         |
| `limit` | `number` | Maximum allowed usage       |

***

### UsageAlert

Dismissible warning banner shown when any feature's usage percentage meets or exceeds the threshold. Includes an upgrade link.

```tsx theme={null}
import { UsageAlert } from '@nozle-js/react';

<UsageAlert
  features={[
    { key: 'api_calls', label: 'API Calls', percentage: 92 },
    { key: 'storage', label: 'Storage', percentage: 45 },
  ]}
  threshold={80}
  upgradeHref="/plans"
/>
```

Only features at or above the threshold are shown. In the example above, only the "API Calls" alert would render (92% >= 80%). The "Storage" alert would not appear (45% \< 80%).

| Prop          | Type                  | Default    | Description                                           |
| ------------- | --------------------- | ---------- | ----------------------------------------------------- |
| `features`    | `UsageAlertFeature[]` | --         | Array of features with their current usage percentage |
| `threshold`   | `number`              | `80`       | Minimum percentage to trigger a warning               |
| `upgradeHref` | `string`              | `'/plans'` | URL for the "Upgrade" link in each alert              |

#### UsageAlertFeature type

| Field        | Type     | Description                      |
| ------------ | -------- | -------------------------------- |
| `key`        | `string` | Unique feature identifier        |
| `label`      | `string` | Human-readable feature name      |
| `percentage` | `number` | Current usage percentage (0-100) |
