Skip to main content

Import

Usage

app/page.tsx

Props

children

React.ReactNode The element that opens the modal. Rendered inline as the trigger; clicking it opens the dialog. Typically a <button>, but any element that accepts onClick works.

flows

FlowConfig — optional The deposit methods to offer, listed in the order you want them rendered within each section. Group them into the picker’s two sections, or pass a flat array and the picker groups it for you. Every method you list renders as a row — the config is authoritative, so list only the methods that work for your chain and currency.
Defaults to ["swap", "bank"]. List cashapp, exchange, or a card provider explicitly to add them. List at most one card provider across the whole config — activating both card[stripe] and card[swapped] throws an error. cashapp is USD-only and throws when rendered with any other currency.
app/page.tsx
Cash App and the exchange onramp are available but not yet recommended for production. card[swapped] is sandbox-only — leave it out of production flows.

Sections

The methods render behind two tabs, crypto first, opening on crypto. The bar appears only when both sections hold at least one method, so configuring a single section gives you a plain list. Each array may only hold methods from its own section. Putting blink under cash throws.

What each shape renders

A full config draws the two tabs with every method you listed:
Picker with Crypto and Cash tabs, crypto tab active, showing Deposit Crypto, Swap to USDC and Deposit from Coinbase

Both sections configured, crypto tab active

Picker with the cash tab active, showing Deposit with Card, Deposit via ACH and Deposit with Cash App

The same config on the cash tab

A single-section config renders a plain list — no tab bar:
Picker with a single Deposit with Card row and no tabs

A cash-only config with just the card method

Picker with a single Deposit Crypto row and no tabs

A crypto-only config with just the blink method

The flat array

A flat Flow[] still works: the picker files each method into its section for you, keeping your order within each section.
That is the same picker as { cash: ["bank"], crypto: ["swap"] } — and the same layout rule applies, so with both sections populated it draws the two tabs. Reach for the grouped form in new integrations; it says what renders where.

balance

number — optional The user’s balance in your app, in the currency you configured. Shown under the Fund Wallet title on the deposit picker, labelled with the name you set on the provider — so a name of Acme reads Acme Balance: $12.34.
app/page.tsx
You own this number. Calm renders what you pass and never reads it from a wallet or an exchange, so a balance your app holds off-chain shows exactly as your app knows it. It follows the number format the user picks in the modal. Leave the prop off and no line renders. Pass 0 to show a zero balance.

Connecting a wallet

A connected wallet is the prerequisite for the whole component. The providers throw during render without one, so mount the tree only once your wallet stack reports a connected address — gate on your provider’s own signal (Privy’s authenticated user with a wallet, wagmi’s useAccount().isConnected, Dynamic’s primaryWallet). Connecting happens in your app’s own UI, before the modal exists. From there the methods open on the first click. If the session behind the wallet is still being created, the flow waits for it rather than prompting. When a session expires while the wallet stays connected, the picker replaces the methods with a loading state until a new session exists.

A cash-only integration

Cash deposits still settle USDC into a wallet, so a wallet still has to exist before the modal can mount — what a cash-first product usually wants is for sign-in to start from an email address rather than a wallet prompt, and that is a setting on your identity provider rather than a prop here. Configure email as the first sign-in method and turn on embedded wallets: your sign-in leads with email, the wallet gets created behind the login, and the deposit methods appear without the user ever handling one. With Privy, both live on your own PrivyProvider:
app/providers.tsx
Every method you list has to be enabled in the Privy dashboard as well. Dynamic has no equivalent ordering option in code: enable email and set the order of its sign-in methods in the Dynamic dashboard, and turn on embedded wallets there too. Adds a Deposit Crypto row to the crypto section. Choosing it opens Blink’s deposit flow over your app; the user pays there, and the destination chain’s payout token arrives in the connected wallet.
app/page.tsx
The row appears wherever you list it. This rail delivers to every supported destination chain — Ethereum, Base, HyperEVM, HyperCore, MegaETH, Monad, and Polygon. The deposit flow renders light or dark to match your modal. It follows the modal surface you set with --calm-popover, so a dark override in Styling carries into it with no extra configuration.

If your app sets a Content-Security-Policy

The deposit flow renders in an iframe, so https://pay.blink.cash has to be in your frame-src allowlist. Add it to the directive you already have rather than replacing it:
Other flows mount iframes of their own, so any origin already in your frame-src must stay there. Dropping one blocks that flow the same way a missing entry blocks this one: the browser refuses the frame and the deposit never starts. Outside production the flow is served from a different origin, so allowlist the one your environment is pointed at as well as the production host. The flow measures the viewport to size itself, reading the viewport height and the bottom safe-area inset, and switching to a sheet layout below a width threshold. Without the standard viewport meta tag a phone reports a desktop-sized virtual viewport, so the flow measures the wrong one and stays in the desktop layout:

SSR

<CalmOnramp> is a client component. In Next.js App Router, render it from a parent that has "use client" at the top: