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

# UI Customization

> Style the embedded offramp screens with the appearance settings you already use for checkout

`CrossmintEmbeddedOfframp` takes an `appearance` prop with the same object as the embedded checkout. For the full list of variables and rules in that object, see the [onramp UI Customization guide](https://docs.crossmint.com/onramp/guides/ui-customization.md). This guide covers which of them the offramp screens use, what each one changes, the default values, and how to test your theme.

## Prerequisites

* **An integration that renders the offramp**: set up `CrossmintEmbeddedOfframp` as in the [React quickstart](https://docs.crossmint.com/offramp/quickstarts/react.md) or the [React Native quickstart](https://docs.crossmint.com/offramp/quickstarts/react-native.md).

## Before You Start

* If checkout already uses your brand, pass the same `appearance` object to the offramp.
* Every setting is optional. A setting that you do not set uses the Crossmint default.
* If a value does not fit the `appearance` schema, such as a color that is not a valid CSS color or a value of the wrong type, the offramp ignores all of your settings and shows the default look.
* If a value contains CSS syntax, such as `;`, `{`, `}`, `<`, `>`, `@`, `url(`, a CSS comment, a backslash, or an unbalanced quote or parenthesis, the offramp drops only that value and uses the default for it. A `fonts` URL on a host that the offramp does not accept (see [Fonts, Sizes, and Corners](#fonts-sizes-and-corners)), or with a quote, a backslash, `<`, `>`, or a line break, is dropped the same way.
* No error message shows in either case, so test after each change.
* The identity verification step inside the offramp uses the same `appearance`. See [Identity Verification UI Customization](https://docs.crossmint.com/identity/ui-customization.md) for those screens.

## Apply Your Theme

Define the `appearance` object once, outside your components:

```typescript offrampAppearance.ts theme={null}
export const offrampAppearance = {
    fonts: [{ cssSrc: "https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap" }],
    variables: {
        fontFamily: "Manrope, system-ui, sans-serif",
        borderRadius: "16px",
        colors: {
            backgroundPrimary: "#0B1020",
            textPrimary: "#F5F7FA",
            textSecondary: "#9AA3B2",
            borderPrimary: "#2A3245",
            accent: "#7C9CFF",
            danger: "#F87171",
            warning: "#FBBF24",
        },
    },
    rules: {
        PrimaryButton: {
            colors: { background: "#7C9CFF", text: "#0B1020" },
            hover: { colors: { background: "#9BB3FF", text: "#0B1020" } },
            disabled: { colors: { background: "#1C2333", text: "#6B7385" } },
        },
    },
};
```

Then pass it to the `appearance` prop. Keep the other props and events from the quickstart.

> [tab] React
>
> ```tsx components/Withdraw.tsx theme={null}
> import {
>     CrossmintEmbeddedOfframp,
>     createCrossmintWalletSigner,
>     useWallet,
>     type EmbeddedOfframpCredentials,
> } from "@crossmint/client-sdk-react-ui";
> import { offrampAppearance } from "./offrampAppearance";
>
> export function Withdraw({ credentials }: { credentials: EmbeddedOfframpCredentials }) {
>     const { wallet } = useWallet();
>     if (wallet == null) {
>         return null;
>     }
>     return (
>         <CrossmintEmbeddedOfframp
>             credentials={credentials}
>             payer={createCrossmintWalletSigner(wallet)}
>             appearance={offrampAppearance}
>         />
>     );
> }
> ```

> [tab] React Native
>
> ```tsx screens/WithdrawScreen.tsx theme={null}
> import {
>     CrossmintEmbeddedOfframp,
>     createCrossmintWalletSigner,
>     useWallet,
>     type EmbeddedOfframpCredentials,
> } from "@crossmint/client-sdk-react-native-ui";
> import { offrampAppearance } from "./offrampAppearance";
>
> export function WithdrawScreen({ credentials }: { credentials: EmbeddedOfframpCredentials }) {
>     const { wallet } = useWallet();
>     if (wallet == null) {
>         return null;
>     }
>     return (
>         <CrossmintEmbeddedOfframp
>             credentials={credentials}
>             payer={createCrossmintWalletSigner(wallet)}
>             appearance={offrampAppearance}
>         />
>     );
> }
> ```

The Enter amount screen with the default look, and with the theme above:

![The offramp's Enter amount screen twice: on the left with the default light look and a green accent, on the right with the dark theme from this guide, a blue accent, and the Manrope font](https://mintcdn.com/crossmint/Vu1-GGAVMpUkNsaw/images/offramp/ui-customization/amount-default-and-themed.jpg?fit=max\&auto=format\&n=Vu1-GGAVMpUkNsaw\&q=85\&s=06438a09da023e2a6b3ddd9cdc01c490)

## Appearance Settings and Where They Apply

Color settings live under `appearance.variables.colors`, and component rules under `appearance.rules`. On every screen, `backgroundPrimary` is the background and `textPrimary` is the default text color. The background fills the whole component, including the padding around the screens. The panel fill is calculated from those two colors (see [Default Colors](#default-colors)).

| Screen | Setting: what it changes |
| :- | :- |
| **Enter amount** | `textPrimary`: title, amount, and keypad. `textSecondary`: currency, estimate, rate, and balance. `accent`: text cursor and the **+** of **Add a bank account**. `danger`: amount errors. `rules.PrimaryButton`: **Next**, and **Use a different bank account** when the selected account cannot be used. The percentage buttons and the bank selector use the panel fill. |
| **Withdraw to** (bank list) | `textPrimary`: title, back arrow, and account names. `textSecondary`: account details, unavailable accounts, and the edit and remove icons. `borderPrimary`: dividers and the tile with the account's initial. `accent`: the selected account's mark and the focus ring of the edit and remove buttons. `danger`: the remove confirmation and removal errors. `rules.PrimaryButton`: **Add a bank account**. The list uses the panel fill. Renaming an account uses `rules.Label`, `rules.Input`, and `rules.PrimaryButton`. |
| **Add a bank account** | `rules.Label`: field labels (default `textPrimary`). `rules.Input`: fields and dropdowns (default `backgroundPrimary`, `borderPrimary`, `textPrimary`, and an `accent` border on focus). `danger`: field errors and the border of a field with an error. `textSecondary`: hints. `accent`: the selected account type (**Checking** or **Savings**). `rules.PrimaryButton`: **Save account**, and **Try again** when the banks for the country cannot be loaded. |
| **Review & confirm** | `textPrimary`: amount and values. `textSecondary`: currency, rate lock, labels, and terms. `borderPrimary`: row dividers, the bank tile, and the track of the rate lock ring. `rules.PrimaryButton`: **Hold to confirm**, **Get new rate**, and the rate lock ring. `warning`: the rate lock ring in its last minute, mixed 80% with `textPrimary`. `danger`: the insufficient funds error. The details panel uses the panel fill. |
| **Progress and status** (Confirm in your wallet, Confirming your deposit, Withdrawal sent, failures) | `textPrimary`: icon, title, and amount. `backgroundPrimary`: the arrow inside the icon. `textSecondary`: the spinning ring and the bank account. `danger`: the failure icon. `accent`: the loading spinner. `rules.PrimaryButton`: **Continue**, **Close**, and **Try again**. `rules.Tab`: **Cancel**, on the screen shown when the order's bank account cannot be loaded. |
| **Identity verification** | The same `appearance`. See [Identity Verification UI Customization](https://docs.crossmint.com/identity/ui-customization.md). |

The checkout settings `rules.DestinationInput`, `rules.ReceiptEmailInput`, `rules.FeeSummary`, and `rules.GlobalMessage` have no effect on the offramp.

## Default Colors

The screens use these colors when you do not set a value. Change only the colors that differ from your brand.

| Element | Setting | Light | Dark |
| :- | :- | :- | :- |
| Background | `backgroundPrimary` | `#FFFFFF` | `#1E2023` |
| Main text | `textPrimary` | `#00150D` | `#FFFFFF` |
| Secondary text | `textSecondary` | `#67797F` | `#939596` |
| Accent | `accent` | `#04AA6D` | `#04AA6D` |
| Borders | `borderPrimary` | `#D1D5DB` | `#3E4043` |
| Error | `danger` | `#DC2626` | `#DC2626` |
| Warning | `warning` | `#E47F0C` | `#E47F0C` |
| Button | `PrimaryButton.colors.background` | `#05B959` | `#05B959` |
| Button text | `PrimaryButton.colors.text` | Your `backgroundPrimary` (`#FFFFFF` by default) | `#FFFFFF` |
| Button hover | `PrimaryButton.hover.colors.background` | `#0BAF5C` | `#0BAF5C` |
| Button hover text | `PrimaryButton.hover.colors.text` | Your `backgroundPrimary` (`#FFFFFF` by default) | `#FFFFFF` |
| Disabled button | `PrimaryButton.disabled.colors.background` | `#E7E9ED` | `#282C2F` |
| Disabled button text | `PrimaryButton.disabled.colors.text` | `#67797F` | `#939596` |
| Panel fill | Calculated, not a setting | `#EDEFEE` | `#2E3032` |

The panel fill is 7% of `textPrimary` mixed into `backgroundPrimary`. In light mode, the button text and its hover text take your `backgroundPrimary` color unless you set them. The hover text is a separate setting from `PrimaryButton.colors.text`, so set `PrimaryButton.hover.colors.text` too when you change the button text.

## Light and Dark Mode

There is no `theme` setting. The offramp picks the light or dark defaults from `variables.colors.backgroundPrimary`:

* An opaque dark color (for example `#1E2023`) selects the dark defaults.
* A light color, a transparent or semi-transparent color, or no value selects the light defaults.

The colors that you set replace only those values. If your app has a light mode and a dark mode, pass the `appearance` that matches the mode the user sees.

> [!TIP]
>
> To get the dark look without picking every color yourself, set `backgroundPrimary` to a dark color and leave the rest unset.

## Fonts, Sizes, and Corners

| Setting | What it changes | Default |
| :- | :- | :- |
| `variables.fontFamily`, `fonts` | The typeface of all text. A `fonts` stylesheet loads only from an `https` URL on `fonts.googleapis.com`, `fonts.gstatic.com`, or `crossmint.com`, so Google Fonts work and a font served from your own domain does not load | Inter |
| `variables.borderRadius` | Corners of fields, buttons, the percentage buttons on Enter amount, the details panel on Review & confirm, and the account type options | `12px` |
| `variables.fontSizeUnit` | Text sizes. The offramp keeps it between `4px` and `5px` | `4px` (40px amount, 16px field text) |
| `variables.spacingUnit` | Space between and inside elements, and the padding around the screens. The offramp keeps it between `2.5px` and `5px` | `3.33px` (24px padding) |

`rules.Label`, `rules.Input`, and `rules.PrimaryButton` can set their own `font`, and `rules.Input` and `rules.PrimaryButton` their own `borderRadius`. For the component's height and width, see [Sizing](https://docs.crossmint.com/offramp/guides/embedded-offramp-reference.md#sizing).

## What You Cannot Change

| Item | How it works |
| :- | :- |
| Withdrawal sent icon | Crossmint green, `#05B959` |
| Selected account mark | A white check on `accent` |
| Hold to confirm progress | A darker shade that fills the button |
| Panel fill | Calculated from your text and background colors |
| Bank list and bank selector corners | Fixed rounded shape |
| Text on the screens | Not editable. The language follows the `locale` prop, or the browser or device language |
| Order and layout of screens | Fixed, including the keypad on Enter amount |

## Test Your Theme

Try your `appearance` values in your own app against a staging project, with staging API keys, before you use them in production.

The component reloads the offramp when the value of `appearance` changes, so each change shows on the next load. Pick the theme before you render the component rather than while a user is withdrawing.

Run these checks in staging before you go live:

* Check light mode and dark mode, if your app has both.
* Check Enter amount, Withdraw to, Add a bank account, and Review & confirm.
* Enter an amount below the minimum and check that the error color shows.
* Check the hover and disabled states of the button. **Next** stays disabled until the user picks a bank account and enters an amount.
* Check the identity verification step with a user who is not verified yet.

## Next Steps

> [card] [Embedded Offramp Reference](https://docs.crossmint.com/offramp/guides/embedded-offramp-reference.md)
>
> Props, events, statuses, and sizing

> [card] [Identity Verification UI Customization](https://docs.crossmint.com/identity/ui-customization.md)
>
> Style the KYC step inside the offramp
