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

# Theming

> Match embedded frames to your brand by passing a brand color and an optional theme object in the frame URL.

Frames adopt your brand at runtime. Pass a brand color and, optionally, a theme object encoded in the URL. The entire palette, border radii, and color scheme derive from these params at request time. WCAG AA contrast is guaranteed when you pass only the brand color. Some theme overrides can lower the contrast ratio below AA, see [Palette overrides](#palette-overrides).

Your application code does not change. You only append params to the frame URL.

## Quick start

```html theme={null}
<iframe
  src="https://platform.moonpay.com/otp-auth?brandColor=FF6B00&clientToken=..."
  width="420"
  height="640"
/>
```

That's it. The frame renders with your brand applied.

For more control, pass a `customTheme` alongside `brandColor` (see [Custom theme](#custom-theme)).

## Initialization parameters

| Property      | Type     | Required | Description                                                                                                                                                                |
| ------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `brandColor`  | `string` | No       | A color value that seeds the entire palette. See [accepted formats](#brand-color-formats). If you omit this, the frame renders with the default MoonPay theme.             |
| `customTheme` | `string` | No       | A URL-encoded JSON object that overrides specific aspects of the theme. Use this for border radius, color scheme, and palette controls. See [Custom theme](#custom-theme). |

### Brand color formats

The simplest form is a six-digit hex with or without the `#`:

```text theme={null}
brandColor=FF6B00         # bare hex (simplest)
brandColor=%23FF6B00      # URL-encoded #FF6B00
brandColor=rgb(255,107,0) # CSS function form
brandColor=0xFF6B00       # numeric hex
```

All forms above produce the same theme. Three-digit shorthand (`#F60`), `rgba()`, `hsl()`, and `oklch()` are also accepted.

If the value cannot be parsed as a color, the param is ignored and the frame falls back to the default theme. No error is surfaced.

## Custom theme

Pass a JSON object describing the overrides you want and append it to the URL as `customTheme`. Build the query string with `URLSearchParams` or wrap the JSON in `encodeURIComponent` so the frame receives a valid URL-encoded payload.

### Building the URL

```javascript theme={null}
const customTheme = JSON.stringify({
  borderRadius: "l",
  colorScheme: "light",
  palette: {
    minContrastRatio: "AAA",
  },
});

const params = new URLSearchParams({
  brandColor: "FF6B00",
  customTheme,
});

const url = `https://platform.moonpay.com/otp-auth?${params.toString()}`;
```

The frame parses the param, validates each field, applies what it understands, and ignores the rest. If the value doesn't parse as JSON, the param is dropped and the frame falls back to the brand-color-only behavior.

### Theme shape

| Field          | Type                                    | Required | Description                                                                                               |
| -------------- | --------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `borderRadius` | `"none"`, `"s"`, `"m"`, `"l"`, `"full"` | No       | Named radius scale applied across the frame. Defaults to `"m"`. See [Border radius](#border-radius).      |
| `colorScheme`  | `"auto"`, `"light"`, `"dark"`           | No       | Locks the frame to a specific color scheme. Defaults to `"auto"`, which follows the user's OS preference. |
| `palette`      | `object`                                | No       | Finer controls for color derivation. See [Palette overrides](#palette-overrides).                         |

Any field you omit keeps its default. Unknown fields are silently ignored, so the shape can grow over time without breaking existing integrations.

### Border radius

`borderRadius` accepts a named scale. Each value maps to the full set of radii the frame uses.

| Value           | Buttons & inputs | Cards | Sheets |
| --------------- | ---------------- | ----- | ------ |
| `none`          | 0                | 0     | 0      |
| `s`             | 4px              | 8px   | 12px   |
| `m` *(default)* | 12px             | 16px  | 24px   |
| `l`             | 16px             | 24px  | 32px   |
| `full`          | 9999px           | 32px  | 40px   |

`full` makes interactive elements fully rounded while keeping larger surfaces visually bounded.

### Color scheme

`colorScheme` controls which mode the frame renders in.

| Value              | Behavior                                                      |
| ------------------ | ------------------------------------------------------------- |
| `auto` *(default)* | Follows the user's OS `prefers-color-scheme` setting.         |
| `light`            | Always renders the light palette regardless of OS preference. |
| `dark`             | Always renders the dark palette regardless of OS preference.  |

Use `light` or `dark` if your app only ships one mode and you want the embedded frame to match.

### Palette overrides

Optional. Use these only if you need finer control than `brandColor` alone provides.

| Field              | Type                                 | Required | Description                                                                                                                   |
| ------------------ | ------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `minContrastRatio` | `"AA"`, `"AAA"`                      | No       | Minimum contrast ratio enforced between text and its background. Defaults to `"AA"` (4.5:1).                                  |
| `semanticColors`   | `{ positive?, negative?, caution? }` | No       | Overrides the base colors used to derive success, error, and warning palettes. Each accepts the same formats as `brandColor`. |

Example:

```javascript theme={null}
const customTheme = {
  palette: {
    minContrastRatio: "AAA",
    semanticColors: {
      positive: "#00C896",
      negative: "#E53E3E",
    },
  },
};
```

## What gets themed

The brand color and theme overrides seed every color and radius the frame uses. The table below shows the role each part of the UI plays.

| Surface            | Color family                                                          |
| ------------------ | --------------------------------------------------------------------- |
| Page background    | Surface, with three nested card tiers                                 |
| Primary CTAs       | Accent (your brand)                                                   |
| Inputs and borders | Surface tiers, with accent on focus                                   |
| Success messaging  | Positive, derived from a green base and adjusted to fit your palette  |
| Error messaging    | Negative, derived from a red base and adjusted to fit your palette    |
| Warning messaging  | Caution, derived from an amber base and adjusted to fit your palette  |
| Text               | Foreground colors paired to each surface for AA contrast              |
| Dark mode          | Inverted counterpart of every color above, derived from the same seed |

## Dark mode

Dark mode is automatic. The frame respects the user's OS `prefers-color-scheme` setting unless you set `colorScheme` to `light` or `dark` in your `customTheme`. Both light and dark themes are derived from the single brand color you pass, so you do not need to supply a separate dark color.

## Accessibility

Every derived text and background pair meets WCAG AA contrast (4.5:1 for normal text) by default. Raise to AAA (7:1) by setting `palette.minContrastRatio` to `"AAA"` in your `customTheme`. If your brand color is extreme enough that the target ratio would be impossible for some role, the frame adjusts that role within a perceptually uniform color space to maintain accessibility. Your brand color itself is preserved as the accent.

## Examples

### OTP authentication, brand color only

```html theme={null}
<iframe
  src="https://platform.moonpay.com/otp-auth?clientToken=...&publicKey=...&channelId=...&brandColor=FF6B00"
/>
```

### Add card, rounded shape, locked to light

```html theme={null}
<!-- customTheme = { borderRadius: "l", colorScheme: "light" } -->
<iframe
  src="https://platform.moonpay.com/add-card?channelId=...&country=GB&brandColor=FF6B00&customTheme=%7B%22borderRadius%22%3A%22l%22%2C%22colorScheme%22%3A%22light%22%7D"
/>
```

### Identity passback, AAA contrast

```html theme={null}
<!-- customTheme = { palette: { minContrastRatio: "AAA" } } -->
<iframe
  src="https://platform.moonpay.com/identity-passback?channelId=...&clientToken=...&brandColor=FF6B00&customTheme=%7B%22palette%22%3A%7B%22minContrastRatio%22%3A%22AAA%22%7D%7D"
/>
```

### Reset, fully rounded, custom status colors

```html theme={null}
<!--
  customTheme = {
    borderRadius: "full",
    palette: {
      semanticColors: { positive: "#00C896", negative: "#E53E3E" }
    }
  }
-->
<iframe
  src="https://platform.moonpay.com/reset?channelId=...&brandColor=FF6B00&customTheme=%7B%22borderRadius%22%3A%22full%22%2C%22palette%22%3A%7B%22semanticColors%22%3A%7B%22positive%22%3A%22%2300C896%22%2C%22negative%22%3A%22%23E53E3E%22%7D%7D%7D"
/>
```

## Troubleshooting

**The frame renders in default MoonPay colors despite passing `brandColor`.**
Check that the value parses as a color. The simplest form is a bare six-digit hex, for example `brandColor=FF6B00`. If you include the `#`, URL-encode it as `%23` (`brandColor=%23FF6B00`).

**My `customTheme` does not apply.**
Verify the value parses as JSON once URL-decoded. Build the query string with `URLSearchParams` or wrap the JSON in `encodeURIComponent` so characters like `{`, `}`, `"`, and `:` are escaped. If the JSON is malformed or the field values are out of range, the entire `customTheme` is dropped and the frame falls back to the brand-color-only behavior.

**Can I pass different colors for light and dark?**
Not yet. Both light and dark themes are derived from the single `brandColor` you provide. Per-mode overrides are on the roadmap.

**How can I confirm theming is applied?**
Open browser devtools and inspect the frame's `<head>`. Look for `<style data-partner-theme>...`. Its presence means the partner theme is active.

## What's next

Per-mode brand overrides are on the roadmap, with a separate `brandColor` for light and dark. The contract is additive, so you will be able to adopt it without changing existing integrations. A specific timeline will be published when it lands.
