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

# React web SDK — @jazadev/react reference

> Official @jazadev/react SDK — JazaProvider, balance, responsive top-up drawer, ledger, and ActionButton gating for Next.js, Remix, and Vite.

Official **React web** client for Jaza.

Use this in your **browser app** with your publishable key (`jz_*_pk_*`) and a **client session** from your backend (`jaza.init` → `getSession` / `authEndpoint`). Never put your secret key in the frontend.

Server-side: [`@jazadev/node`](/sdks/node) — `init` for the handshake, `consume` for debits. The SDK mints short-lived top-up JWTs from the client session.

<Note>
  Source and examples live on GitHub: [packages/@jaza-react](https://github.com/josumung999/jaza-packages/tree/main/packages/@jaza-react). The Next.js sample under `example/` mirrors the full sign-in → init → top-up → consume path.
</Note>

## Install

```bash theme={null}
npm install @jazadev/react @base-ui/react
```

Import styles once (root layout / entry):

```ts theme={null}
import '@jazadev/react/styles.css';
```

Recommended for Base UI drawers on iOS Safari:

```css theme={null}
body {
  position: relative;
}
```

Peers: `react` ≥18, `react-dom` ≥18, `@base-ui/react` ≥1 (Drawer only — **no** Tailwind or shadcn required).

## Framework notes

| Framework                        | Notes                                                                  |
| -------------------------------- | ---------------------------------------------------------------------- |
| **Next.js App Router**           | Import SDK only from **Client Components** (`'use client'`).           |
| **Next.js Pages / Remix / Vite** | Use normally in app code.                                              |
| Handshake                        | Same as mobile: `getSession` or `authEndpoint` returning `InitResult`. |

## Provider setup

```tsx theme={null}
'use client'; // Next App Router

import {
  JazaProvider,
  type InitResult,
} from '@jazadev/react';
import '@jazadev/react/styles.css';

export function BillingProvider({ children }: { children: React.ReactNode }) {
  return (
    <JazaProvider
      publishableKey={process.env.NEXT_PUBLIC_JAZA_PUBLISHABLE_KEY!}
      getSession={async () => {
        const res = await fetch('/api/jaza/init', {
          method: 'POST',
          credentials: 'include',
        });
        if (!res.ok) throw new Error('Jaza init failed');
        return (await res.json()) as InitResult;
      }}
      onAuthError={(error) => {
        console.warn('Jaza session failed', error.message);
      }}
      theme="system"
    >
      {children}
    </JazaProvider>
  );
}
```

Host route:

```ts theme={null}
// POST /api/jaza/init
const result = await jaza.init({ customerId: user.jazaCustomerId });
return Response.json(result);
```

## Top-up drawer

Checkout uses a **Base UI Drawer**:

* **≤767px** — slides up from the bottom
* **Desktop** — slides in from the right

Same offer → payment → processing flow as the React Native SDK.

## Widgets and customization

Default UI works out of the box. Pass a **children render prop** (or `ItemComponent` on the ledger) to wrap your own components while reusing SDK state.

### `JazaBalance`

```tsx theme={null}
import { JazaBalance } from '@jazadev/react';

{/* Default */}
<JazaBalance />

{/* Custom */}
<JazaBalance>
  {({ balanceCredits, loading, error, refresh }) => (
    <button type="button" onClick={() => void refresh()}>
      {loading ? '…' : `${balanceCredits ?? 0} credits`}
      {error ? <span>{error}</span> : null}
    </button>
  )}
</JazaBalance>
```

### `JazaTopUpButton`

```tsx theme={null}
{/* Default */}
<JazaTopUpButton label="Top up credits" />

{/* Custom */}
<JazaTopUpButton label="Top up">
  {({ onPress, loading, disabled, label, error }) => (
    <button type="button" onClick={onPress} disabled={disabled || loading}>
      {loading ? 'Opening…' : label}
      {error ? <span>{error}</span> : null}
    </button>
  )}
</JazaTopUpButton>
```

### `JazaLedger`

```tsx theme={null}
{/* Default */}
<JazaLedger mode="preview" limit={5} />

{/* Custom row */}
<JazaLedger
  mode="preview"
  limit={5}
  ItemComponent={({ title, subtitle, credits, direction }) => (
    <div>
      <strong>{title}</strong>
      <span>{subtitle}</span>
      <span>
        {direction === 'CREDIT' ? '+' : '−'}
        {credits}
      </span>
    </div>
  )}
/>
```

### `JazaActionButton`

Gate only — your BFF must call `jaza.consume`. See [Gating actions](/guides/gating-actions).

```tsx theme={null}
{/* Default */}
<JazaActionButton
  featureCode="SEND_MESSAGE"
  label="Send message"
  onPress={async () => {
    await fetch('/api/messages', { method: 'POST', credentials: 'include' });
  }}
/>

{/* Custom */}
<JazaActionButton featureCode="SEND_MESSAGE" onPress={doSend}>
  {({ onPress, canAfford, cost, loading, disabled, label }) => (
    <button type="button" onClick={onPress} disabled={disabled || loading}>
      {label} ({cost ?? '…'} cr)
      {!canAfford ? ' — top up' : ''}
    </button>
  )}
</JazaActionButton>
```

## Sandbox → live

TEST and LIVE customer ids are isolated. Remint with `createCustomer` under live keys and overwrite your host DB. See [Authentication](/guides/authentication#going-live-remint-customerid).

## Next steps

* [Handshake](/guides/handshake) · [Top-up](/guides/top-up) · [Gating actions](/guides/gating-actions) · [Node SDK](/sdks/node) · [React Native SDK](/sdks/react-native)
