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

# Embedding and customization

> Put a Connect widget on your page, and restyle it to match.

A Connect widget is an iframe.
You mint a short-lived access token on your backend, put it in the URL, and style the widget with query parameters.

Try the parameters interactively on the [playground](/connect/playground), which generates the snippet for you.

## Embedding

```html theme={"system"}
<iframe
  src="https://app.sideshift.app/widget/payout/{ACCESS_TOKEN}"
  width="100%"
  height="500"
  frameborder="0"
  allow="payment; camera; microphone"
  title="SideShift Payout Widget"
></iframe>
```

Swap `payout` for `payin` to embed the pay-in widget, and give it more room while you are there.
Pay-in is the taller of the two: the Connect settings screen generates `height="600"` for it against `500` for payout, and a flat 500 clips it.

The `allow` attribute matters.
Identity verification uses the camera, and card flows may need payment permissions.
Without it, those steps fail inside the frame rather than falling back.

<Warning>
  Mint the token on your backend, never in the browser.
  Tokens default to a one-hour lifetime and cannot exceed 24 hours, so treat them as short-lived credentials rather than configuration.
</Warning>

### Height

The examples use a fixed `height="500"`, which is the simplest thing that works.

The widget also posts its height to the parent whenever its content changes, so you can size the frame to fit:

```js theme={"system"}
window.addEventListener('message', (event) => {
  const msg = event.data;
  if (msg?.source !== 'sideshift-connect') return;

  if (msg.type === 'widget:resize') {
    iframe.style.height = msg.data.height + 'px';
  }
});
```

One caveat: the widget's own container defaults to `min-height: 100vh` so that short content still covers the frame, which means it never reports a height smaller than the frame you gave it.
If you want the frame to shrink to its content, set `minHeight` explicitly, for example `?minHeight=0`.

### Overlays

Identity verification and withdrawal confirmation open as modals.
Inside a short iframe they are cramped, so the widget announces them and lets you expand the frame:

```js theme={"system"}
if (msg.type === 'widget:overlay_open') expand();
if (msg.type === 'widget:overlay_close') restore();
```

Without a listener the modal still works, it just fills whatever space the iframe has.

## Customization

Every option below is a query parameter on the widget URL.

### Theme and colour

| Parameter      | Values                   | Default  |
| -------------- | ------------------------ | -------- |
| `theme`        | `light`, `dark`          | `light`  |
| `primaryColor` | hex, with or without `#` | `3D8CFA` |
| `borderRadius` | `0`-`50`                 | `12`     |

<Warning>
  `theme=auto` is accepted but not implemented.
  It resolves to the light palette rather than following the system or page theme.
  To follow the reader's theme, detect it yourself and set `theme=light` or `theme=dark` on the iframe URL.
</Warning>

```js theme={"system"}
const theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
iframe.src = `https://app.sideshift.app/widget/payout/${token}?theme=${theme}`;
```

Reassigning `src` reloads the frame, so change the theme before the reader starts a withdrawal rather than during one.

### Individual colours

For finer control, override any of these directly.
Each takes a hex value: `background`, `cardBackground`, `inputBackground`, `textPrimary`, `textSecondary`, `textMuted`, `textInverted`, `border`, `borderFocus`, `borderSelected`, `primary`, `primaryHover`, `primaryLight`, `success`, `successBackground`, `error`, `errorBackground`, `warning`, `warningBackground`, `iconColor`, `iconMuted`, `divider`, `skeleton`.

`primary` wins over `primaryColor` when both are present.

### Typography, spacing, buttons and cards

| Group      | Parameters                                                                                                                         |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Typography | `fontFamily`, `headingSize`, `bodySize`, `labelSize`, `amountSize`, `smallSize`                                                    |
| Spacing    | `borderRadiusSmall`, `borderRadiusLarge`, `cardPadding`, `containerPadding`, `elementSpacing`                                      |
| Buttons    | `buttonHeight`, `buttonFontSize`, `buttonFontWeight`, `buttonTextTransform`, `buttonShadow`, `buttonShadowHover`, `buttonGradient` |
| Cards      | `cardShadow`, `cardBorderWidth`, `cardBorderStyle`                                                                                 |
| Layout     | `maxWidth`, `minHeight`                                                                                                            |

`buttonTextTransform` accepts `none`, `uppercase` or `capitalize`.
`cardBorderStyle` accepts `solid`, `dashed` or `none`.
The rest take CSS values and are passed through.

### Sections

| Parameter                                                                           | Widget |
| ----------------------------------------------------------------------------------- | ------ |
| `showBalance`, `showHistory`, `showWithdrawHeader`, `showKycHeader`, `showKycSteps` | Payout |
| `showQuickAmounts`, `showFeeBreakdown`                                              | Pay-in |

Send `false` to hide a section.

### Labels

Payout: `payoutTitle`, `balanceLabel`, `withdrawButton`, `historyTitle`, `historyDescription`, `settingsTitle`, `settingsDescription`, `kycButton`, `kycTitle`, `kycVerifiedTitle`, `kycVerifiedDescription`.

Pay-in: `payinTitle`, `paymentMethodsTitle`, `addMethodButton`, `depositButton`, `amountLabel`, `feeLabel`, `totalLabel`, `walletLabel`, `successTitle`, `successMessage`.

In `depositButton`, `$X` is replaced with the formatted amount.

<Note>
  Label values are decoded twice, so a label that already round-tripped through an encoder comes back intact.
  A literal `%` in a hand-written URL is passed through as typed rather than decoded again.
  If you want a label to contain `%20` as visible text rather than a space, encode it as `%2520`.
</Note>

## Three behaviours worth knowing

These surprise people, and each one is easy to mistake for a bug in your own code.

**Only the literal string `false` hides a section.**
`showBalance=0` and `showBalance=no` both read as true.

**An out-of-range `borderRadius` is discarded, not clamped.**
`borderRadius=99` falls back to the 12px default rather than rendering at 50.

**The `config` parameter replaces everything else.**
You can pass a whole theme as URL-encoded JSON in `config`.
If it parses, every individual parameter on the URL is ignored, so pick one approach rather than mixing them.
If it fails to parse, the widget falls back to the individual parameters rather than erroring.

## Events

The widget posts these to the parent window.
Every message has `source: 'sideshift-connect'`, a `type`, and sometimes `data`.

`widget:loaded`, `widget:error`, `widget:resize`, `widget:overlay_open`, `widget:overlay_close`, `session:expired`, `payout:withdraw_initiated`, `payout:kyc_completed`, `payin:deposit_initiated`, `payin:deposit_completed`, `payin:deposit_failed`, `payin:method_added`, `payin:amount_changed`.

<Warning>
  Three more names exist in the event type but are never sent by the widget: `payout:withdraw_completed`, `payout:balance_updated` and `payout:kyc_started`.
  Do not build on them.
  `payout:withdraw_completed` is the trap, because it is the natural place to mark a payout finished and a handler for it will simply never run.
  Confirm completed withdrawals from the `withdrawal.completed` webhook instead, which is server-to-server and survives the reader closing the tab.
</Warning>

`session:expired` is the one to handle.
Tokens are short-lived, so mint a fresh one and reload the frame when it arrives.
