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

# React

> Install and configure the latest version of @metrifox/react-sdk: Customer Portal, Pricing Table, unified theme API, and the React playground.

Use the live playground to preview Customer Portal and Pricing Table before integrating the SDK into your application: **[React SDK Playground](https://playground.metrifox.com/react)**.

***

## Installation

```bash theme={null}
npm install @metrifox/react-sdk
# or
pnpm add @metrifox/react-sdk
# or
yarn add @metrifox/react-sdk
```

***

## Setup

Initialize once with your **client key** (from the Metrifox dashboard) before rendering any SDK components.

```tsx theme={null}
import { metrifoxInit } from "@metrifox/react-sdk";

metrifoxInit({
  clientKey:
    process.env.NEXT_PUBLIC_METRIFOX_APP_SECRET ||
    process.env.REACT_APP_METRIFOX_APP_SECRET ||
    import.meta.env?.VITE_METRIFOX_APP_SECRET ||
    "your-client-key",
});
```

<Note>
  Works with **React**, **Next.js**, and other setups on **React 18+**. Environment variable names may vary by framework; use the public prefix your bundler expects so the key is available in the browser.
</Note>

***

## Widgets

### `CustomerPortal`

Renders a customer dashboard: plans, subscriptions, billing, credits, and related sections.

```tsx theme={null}
import { CustomerPortal } from "@metrifox/react-sdk";

const MyCustomPlan = ({ foo }: { foo?: string }) => (
  <div>Custom plan section. Foo: {foo}</div>
);

export default function MyPortalPage() {
  return (
    <CustomerPortal
      customerKey="your-customer-key"
      sectionsConfig={[
        { key: "subscription" },
        { key: "plan", component: MyCustomPlan, props: { foo: "bar" } },
        { key: "billingHistory", hidden: true },
      ]}
      theme={{ general: { linkColor: "#2563eb" } }}
    />
  );
}
```

Optional **`theme` prop**: pass a `CustomerPortalTheme` partial to override or extend the global theme from `metrifoxInit` for this mount only (merged with defaults and global config).

#### Section configuration

| Property    | Type                      | Description                               |
| ----------- | ------------------------- | ----------------------------------------- |
| `key`       | `SectionKey`              | Section identifier (see table below)      |
| `hidden`    | `boolean`                 | When `true`, the section is not shown     |
| `component` | `React.ComponentType`     | Replace the default section UI            |
| `props`     | `Record<string, unknown>` | Props passed to custom or default section |

#### Built-in section keys

| Key                | Description                  |
| ------------------ | ---------------------------- |
| `upcomingInvoice`  | Next invoice details         |
| `subscription`     | Active subscription overview |
| `creditBalance`    | Wallet / credit balance      |
| `entitlementUsage` | Usage meters                 |
| `paymentOverview`  | Payment methods and summary  |
| `billingHistory`   | Past invoices / transactions |
| `plan`             | Current plan                 |

#### Section anchors (deep links)

Each section is wrapped in `<section id="...">` for hash links (for example `https://yourapp.com/billing#billing-history`).

| Anchor ID            | Section key        |
| -------------------- | ------------------ |
| `#upcoming-invoice`  | `upcomingInvoice`  |
| `#subscription`      | `subscription`     |
| `#credit-balance`    | `creditBalance`    |
| `#entitlement-usage` | `entitlementUsage` |
| `#payment-overview`  | `paymentOverview`  |
| `#billing-history`   | `billingHistory`   |
| `#plan`              | `plan`             |

***

### `PricingTable`

Renders subscription plans and one-time purchases for a product.

```tsx theme={null}
import { PricingTable } from "@metrifox/react-sdk";

export default function PricingPage() {
  return (
    <PricingTable
      checkoutUsername="your-checkout-username"
      productKey="your-product-key"
      theme={{ plans: { planCards: { background: "#ffffff" } } }}
    />
  );
}
```

#### Props

| Property              | Type                | Required | Default | Description                                                           |
| --------------------- | ------------------- | -------- | ------- | --------------------------------------------------------------------- |
| `checkoutUsername`    | `string`            | Yes      | —       | Checkout username from **Settings → Checkout**                        |
| `productKey`          | `string`            | Yes      | —       | Product identifier from the product page                              |
| `customerKey`         | `string`            | No       | —       | Customer identifier from the customer page                            |
| `plansOnly`           | `boolean`           | No       | `false` | Only subscription plans                                               |
| `singlePurchasesOnly` | `boolean`           | No       | `false` | Only one-time purchases                                               |
| `showTabHeader`       | `boolean`           | No       | `true`  | Tab header for plans vs single purchases                              |
| `embedCheckout`       | `boolean`           | No       | `false` | Embed the checkout flow within the pricing table                      |
| `theme`               | `PricingTableTheme` | No       | —       | Per-instance theme override (merged with global `theme.pricingTable`) |

<Note>
  If both `plansOnly` and `singlePurchasesOnly` are `false` or omitted, **both** plans and single purchases are shown.

  Embedded checkout only works if `checkoutSettings.signup_redirect_url` is not set or `customerKey` is provided.
</Note>

***

## Styling

Import global SDK styles once (for example in `src/main.tsx`, `app/layout.tsx`, or `_app.tsx`):

```tsx theme={null}
import "@metrifox/react-sdk/dist/styles.css";
```

<Warning>This stylesheet is required for correct layout and components.</Warning>

### Theme configuration

Theming is driven by a single object passed to `metrifoxInit`:

```ts theme={null}
metrifoxInit({
  clientKey: "...",
  theme: {
    customerPortal?: CustomerPortalTheme;
    pricingTable?: PricingTableTheme;
  },
});
```

Every theme field is **optional**. Omitted keys fall back to SDK defaults. `<CustomerPortal />` and `<PricingTable />` may each take an optional `theme` prop to layer overrides for that instance only (deep-merged with global config and defaults).

<Note>
  Values accept any valid CSS value: hex, `rgb()`, `hsl()`, named colors, lengths (`px`, `rem`, `%`), and `font-family` strings. The skeletons below use `""` as placeholders — replace only the keys you want to override.
</Note>

### Customer Portal theme (`CustomerPortalTheme`)

Every field is optional. Groups and roles:

| Group       | Role                                                                           |
| ----------- | ------------------------------------------------------------------------------ |
| `general`   | Page canvas: links, background, radius, font, container padding                |
| `tabs`      | Tab bar: backgrounds, borders, active / inactive text                          |
| `select`    | Dropdowns: trigger, caret, menu, options                                       |
| `sections`  | Section cards: surfaces, typography, usage bars, summary balance, empty states |
| `buttons`   | Primary and secondary action buttons                                           |
| `lineItems` | Subscription line-item rows (parent and child)                                 |
| `popover`   | Action menus on line items (trigger icon, dropdown menu)                       |
| `tables`    | Data tables (e.g. billing history): header, rows, expand icon                  |
| `modals`    | Modal overlay, shell, header/title/description, footer actions                 |
| `banners`   | Info banners (e.g. scheduled plan change, undo cancellation)                   |
| `plans`     | Plan cards embedded in the portal: current plan, cards, features, toggle, tags |

**Full `CustomerPortalTheme` reference** — all string fields shown as `""`. Omit any group or key you do not need to customize.

```ts theme={null}
customerPortal: {
  general: {
    linkColor: "",
    fontFamily: "",
    borderRadius: "",
    backgroundColor: "",
    containerPadding: "",
  },
  tabs: {
    tabBackground: "",
    tabBorderColor: "",
    activeTabBackground: "",
    activeTabTextColor: "",
    inactiveTabTextColor: "",
  },
  select: {
    background: "",
    borderColor: "",
    textColor: "",
    caretColor: "",
    dropdownBackground: "",
    dropdownBorderColor: "",
    optionTextColor: "",
    optionHoverBackground: "",
  },
  sections: {
    background: "",
    padding: "",
    borderColor: "",
    borderRadius: "",
    emptyTextColor: "",
    iconBackground: "",
    iconColor: "",
    usage: {
      barColor: "",
      trackColor: "",
    },
    content: {
      background: "",
      padding: "",
      borderColor: "",
      borderRadius: "",
    },
    summaryBalance: {
      background: "",
      padding: "",
      borderColor: "",
      borderRadius: "",
      label: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
      value: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
      unit: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
    },
    header: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    label: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    value: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
  },
  buttons: {
    primary: {
      backgroundColor: "",
      border: {
        color: "",
        width: "",
        radius: "",
      },
      typography: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
    },
    secondary: {
      backgroundColor: "",
      border: {
        color: "",
        width: "",
        radius: "",
      },
      typography: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
    },
  },
  lineItems: {
    parentRow: {
      background: "",
      borderColor: "",
      borderRadius: "",
      expandIconColor: "",
      expandIconSize: "",
      typography: {
        label: {
          fontSize: "",
          fontWeight: "",
          color: "",
        },
        quantity: {
          fontSize: "",
          color: "",
        },
      },
      spacing: {
        paddingTop: "",
        paddingRight: "",
        paddingBottom: "",
        paddingLeft: "",
      },
    },
    childRow: {
      background: "",
      borderColor: "",
      borderRadius: "",
      typography: {
        label: {
          fontSize: "",
          fontWeight: "",
          color: "",
        },
        quantity: {
          fontSize: "",
          color: "",
        },
      },
      spacing: {
        paddingTop: "",
        paddingRight: "",
        paddingBottom: "",
        paddingLeft: "",
      },
    },
  },
  popover: {
    trigger: {
      background: "",
      iconColor: "",
      borderColor: "",
      borderRadius: "",
    },
    menu: {
      background: "",
      borderColor: "",
      borderRadius: "",
      itemPadding: "",
      itemHoverBackground: "",
      itemDividerColor: "",
      typography: {
        fontSize: "",
        fontWeight: "",
        color: "",
        dangerColor: "",
      },
    },
  },
  tables: {
    headerBackground: "",
    headerTextColor: "",
    rowBackgroundOdd: "",
    rowBackgroundEven: "",
    rowTextColor: "",
    borderColor: "",
    cellPadding: "",
    expandIconColor: "",
    typography: {
      fontSize: "",
      fontWeight: "",
      headerFontSize: "",
      headerFontWeight: "",
    },
  },
  modals: {
    overlayColor: "",
    background: "",
    borderColor: "",
    borderRadius: "",
    closeButtonColor: "",
    header: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    title: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    description: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    footer: {
      primary: {
        backgroundColor: "",
        textColor: "",
        borderColor: "",
        borderWidth: "",
        borderRadius: "",
      },
      secondary: {
        backgroundColor: "",
        textColor: "",
        borderColor: "",
        borderWidth: "",
        borderRadius: "",
      },
    },
  },
  banners: {
    info: {
      background: "",
      textColor: "",
      borderRadius: "",
      buttonBorderColor: "",
      buttonTextColor: "",
    },
  },
  plans: {
    currentPlanCard: {
      header: {
        background: "",
        textColor: "",
      },
      gradientColor: "",
      borderRadius: "",
    },
    planCards: {
      background: "",
      border: {
        color: "",
        width: "",
        radius: "",
      },
      header: {
        background: "",
        textColor: "",
      },
      description: {
        textColor: "",
        textButtonColor: "",
      },
      price: {
        amountColor: "",
        primaryTextColor: "",
        secondaryTextColor: "",
        background: "",
        borderColor: "",
      },
    },
    planFeatures: {
      textColor: "",
      iconColor: "",
    },
    planButton: {
      background: "",
      textColor: "",
    },
    planToggle: {
      background: "",
      activeBackground: "",
      activeText: "",
      inactiveText: "",
    },
    planTags: {
      freeTrialBackground: "",
      freeTrialText: "",
    },
  },
},
```

### Font customization

Set `customerPortal.general.fontFamily` or `pricingTable.general.fontFamily` to any CSS `font-family` string. **Your app must load the font** (Google Fonts, `@font-face`, etc.) before or as the SDK mounts to avoid FOUT.

### Pricing Table theme (`PricingTableTheme`)

Plan-related tokens are nested under **`plans`** (for example `plans.planCards`, `plans.planToggle`).

When Pricing Table is embedded in Customer Portal, plan styling follows **`theme.customerPortal.plans`** so portal and table stay consistent.

Every field is optional. Groups and roles:

| Group         | Role                                                                                                |
| ------------- | --------------------------------------------------------------------------------------------------- |
| `general`     | Page-level font family                                                                              |
| `plans`       | All plan UI: `currentPlanCard`, `planCards`, `planFeatures`, `planButton`, `planToggle`, `planTags` |
| `tabs`        | Plans vs single-purchase tab bar                                                                    |
| `select`      | Dropdowns in the pricing flow (interval, quantity, etc.)                                            |
| `checkoutBar` | Sticky checkout summary bar                                                                         |
| `modals`      | Confirmation / checkout modals                                                                      |

**Full `PricingTableTheme` reference** — all string fields shown as `""`. Omit any group or key you do not need to customize.

```ts theme={null}
pricingTable: {
  general: {
    fontFamily: "",
  },
  plans: {
    currentPlanCard: {
      header: {
        background: "",
        textColor: "",
      },
      gradientColor: "",
      borderRadius: "",
    },
    planCards: {
      background: "",
      border: {
        color: "",
        width: "",
        radius: "",
      },
      header: {
        background: "",
        textColor: "",
      },
      description: {
        textColor: "",
        textButtonColor: "",
      },
      price: {
        amountColor: "",
        primaryTextColor: "",
        secondaryTextColor: "",
        background: "",
        borderColor: "",
      },
    },
    planFeatures: {
      textColor: "",
      iconColor: "",
    },
    planButton: {
      background: "",
      textColor: "",
      secondaryBackground: "",
      secondaryTextColor: "",
      textButtonColor: "",
    },
    planToggle: {
      background: "",
      activeBackground: "",
      activeText: "",
      inactiveText: "",
    },
    planTags: {
      freeTrialBackground: "",
      freeTrialText: "",
    },
  },
  tabs: {
    inactiveText: "",
    activeText: "",
    indicator: "",
    borderColor: "",
  },
  select: {
    background: "",
    borderColor: "",
    textColor: "",
    placeholderColor: "",
    caretColor: "",
    dropdownBackground: "",
    dropdownBorderColor: "",
    optionTextColor: "",
    optionHoverBackground: "",
  },
  checkoutBar: {
    background: "",
    borderColor: "",
    textColor: "",
    buttonBackground: "",
    buttonTextColor: "",
  },
  modals: {
    overlayColor: "",
    background: "",
    borderColor: "",
    borderRadius: "",
    closeButtonColor: "",
    header: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    title: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    description: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    footer: {
      primary: {
        backgroundColor: "",
        textColor: "",
        borderColor: "",
        borderWidth: "",
        borderRadius: "",
      },
      secondary: {
        backgroundColor: "",
        textColor: "",
        borderColor: "",
        borderWidth: "",
        borderRadius: "",
      },
    },
  },
},
```

### Applying your theme

You can set theme in two places. Both accept partial objects, only include the keys you want to change.

#### Global (all widgets)

Pass `theme` to `metrifoxInit` once at app startup. Every `<CustomerPortal />` and `<PricingTable />` inherits it:

```tsx theme={null}
import { metrifoxInit } from "@metrifox/react-sdk";

metrifoxInit({
  clientKey: "your-client-key",
  theme: {
    customerPortal: {
      general: { linkColor: "#2563eb" },
      buttons: {
        primary: { backgroundColor: "#2563eb" },
      },
    },
    pricingTable: {
      plans: {
        planButton: { background: "#2563eb", textColor: "#ffffff" },
      },
      checkoutBar: {
        buttonBackground: "#2563eb",
        buttonTextColor: "#ffffff",
      },
    },
  },
});
```

#### Per component (single instance)

Pass `theme` on an individual widget to override the global config for that mount only:

```tsx theme={null}
<CustomerPortal customerKey="..." theme={{ general: { linkColor: "#1d4ed8" } }} />
<PricingTable
  checkoutUsername="..."
  productKey="..."
  theme={{ plans: { planCards: { background: "#f8fafc" } } }}
/>
```

***

## Changelog and support

Release history and breaking-change notes: **[SDK changelog](/sdks/changelog)**.\
Package README and issue trackers live on the public npm scope **`@metrifox/react-sdk`**.\
Broader product docs: [docs.metrifox.com](https://docs.metrifox.com).
