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

# Advanced UI

> Restyle the offer sheet with theme colors, or replace the reward sheet's headline and buttons with your own elements: the full appearance contract on PlacementOptions.

The sheet ships styled, responsive, and accessible, and most integrations never change it.
Reach for `appearance` when the sheet has to sit inside your brand: your palette on the
surfaces, your typography on the headline, or your own element where the SDK's button was.
Every field is optional, and **omitting `appearance` entirely renders exactly the default
sheet**. Nothing on this page is required for a working integration.

`appearance` is a per-presentation option on
[`PlacementOptions`](/publishers/web/sdk-reference/placement#placementoptions):

```javascript theme={null}
import Encore from '@encorekit/web-sdk';

const result = await Encore.placement('milestone_reached', {
  useCase: 'rewardUsers',
  appearance: { accentColor: '#0A6' },
}).show();
```

## The two tiers

The config has two tiers, and they compose.

**Theme colors** set the SDK's CSS custom properties, so one value repaints everything
derived from it: muted text, secondary surfaces, focus rings.

| Field                  | What it moves                                                                                            |
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
| `backgroundColor`      | The sheet's canvas, and every surface derived from it.                                                   |
| `textColor`            | The primary ink, from which muted and secondary text are derived.                                        |
| `accentColor`          | Focus rings, links, and the Reduce Churn CTA.                                                            |
| `modalBackgroundColor` | The modal container only. Narrower than `backgroundColor`: use it to repaint the sheet and nothing else. |

**Reward sheet overrides** replace or restyle one element each. They are read by the
`'rewardUsers'` composition, the one with a headline block, a CTA pair, and page dots.
`'reduceChurn'` renders the bare carousel and honors the theme colors and
`modalBackgroundColor` only.

| Field            | Kind  | What it fills                                     |
| ---------------- | ----- | ------------------------------------------------- |
| `headline`       | slot  | The reward sheet's primary heading.               |
| `subheadline`    | slot  | The line under it.                                |
| `claimButton`    | slot  | The "Claim gift" CTA. The SDK keeps the action.   |
| `declineButton`  | slot  | The "No thanks" button. The SDK keeps the action. |
| `dotColor`       | color | The page indicator's inactive dots.               |
| `dotActiveColor` | color | The page indicator's active dot.                  |

## The three slot forms

Each slot (`headline`, `subheadline`, `claimButton`, `declineButton`) takes one of three
forms. Pick the cheapest one that does the job.

```typescript theme={null}
type TextSlot   = TextAppearance   | HTMLElement | AppearanceRenderer;
type ButtonSlot = ButtonAppearance | HTMLElement | AppearanceRenderer;
type AppearanceRenderer = (context: AppearanceSlotContext) => HTMLElement | null | undefined;
```

All of these are exported from `@encorekit/web-sdk`, so a TypeScript integration can type
against them directly.

### A styled config object

The most common case: custom copy, font, and color, with no DOM knowledge required. The
SDK keeps its own element, and with it every default style and accessibility attribute, and
applies your values on top.

```javascript theme={null}
import Encore from '@encorekit/web-sdk';

await Encore.placement('milestone_reached', {
  useCase: 'rewardUsers',
  appearance: {
    modalBackgroundColor: '#101014',
    dotColor: '#3A3A44',
    dotActiveColor: '#FFD166',
    headline: {
      text: 'Nice work, Sam!',
      color: '#FFD166',
      fontFamily: '"Helvetica Neue", Arial, sans-serif',
      fontSize: '28px',
      fontWeight: 700,
    },
    claimButton: {
      backgroundColor: '#FFD166',
      color: '#101014',
      borderRadius: '999px',
    },
  },
}).show();
```

Text slots accept `text`, `color`, `fontFamily`, `fontSize`, `fontWeight`,
`letterSpacing`, and `textAlign`. Button slots accept the same fields plus
`backgroundColor`, `borderColor`, and `borderRadius`. A `text` value that is blank or
whitespace-only falls **through** to the copy Encore resolved rather than blanking the
slot, the same rule `PlacementOptions.headline` follows; a non-blank `text` beats it.

### A ready element

Hand the slot an `HTMLElement` to render something that is not text at all. The SDK mounts
your element inside its own semantic element, which keeps the heading's id and the
sheet's accessible name intact.

```javascript theme={null}
import Encore from '@encorekit/web-sdk';

const badge = document.createElement('div');
badge.className = 'streak-badge';
badge.append('🔥 5-week streak');

await Encore.placement('milestone_reached', {
  useCase: 'rewardUsers',
  appearance: { subheadline: badge },
}).show();
```

Build a fresh element per slot and per presentation. One element can fill only one slot:
supplying the same element twice keeps the SDK's default for the second one.

### A renderer

A `(context) => HTMLElement` function runs per presentation with the SDK's context in
hand:

| Context field    | What it carries                                                         |
| ---------------- | ----------------------------------------------------------------------- |
| `text`           | The copy the SDK resolved for this slot, after its full fallback chain. |
| `useCase`        | Why the sheet is presenting.                                            |
| `defaultElement` | The element the SDK would have rendered.                                |

Return an element to replace the default, return `defaultElement` (mutated) to decorate
it, or return `null` to keep the default as is.

```javascript theme={null}
import Encore from '@encorekit/web-sdk';

await Encore.placement('milestone_reached', {
  useCase: 'rewardUsers',
  appearance: {
    // Replace: build your own CTA around the copy the SDK resolved.
    claimButton: ({ text }) => {
      const el = document.createElement('span');
      el.className = 'my-cta';
      el.textContent = text;
      return el;
    },
    // Decorate: keep the SDK's element, styles, and accessibility wiring.
    declineButton: ({ defaultElement }) => {
      defaultElement.classList.add('my-ghost-button');
      return defaultElement;
    },
  },
}).show();
```

## What you can rely on

* **Omitting `appearance` changes nothing.** The sheet renders exactly what it rendered
  before this config existed, byte for byte.
* **The SDK keeps the action.** On `claimButton` and `declineButton` you supply appearance
  only. Your element is mounted inside an SDK-owned control that carries the role, the tab
  stop, and the listener; the listener runs in the capture phase, so the SDK's
  claim or decline behavior runs first and a handler you attached to your own element does
  not run at all. React to the outcome through `show()`'s resolved
  [`ShowResult`](/publishers/web/sdk-reference/show), never through your own click
  handlers.
* **The SDK also keeps the gate.** Because the claim is the SDK's, so is whether it is
  available: turning
  [`Encore.isClaimEnabled`](/publishers/web/sdk-reference/is-claim-enabled) off makes your
  claim button unavailable along with everything else. Your element's host carries
  `aria-disabled="true"` rather than the native `disabled` attribute, which means nothing
  outside a `<button>`. Target both forms if you style the unavailable state yourself.
* **A bad override costs you the override, never the sheet.** Every renderer runs inside
  a guard, and every color, font, and size goes through an allow-list. A throw, a
  non-element return, or a malformed value falls back to the SDK's default element.
  Nothing you pass can take down the modal or the host page.
* **`'reduceChurn'` is theme-only.** The bare carousel honors `backgroundColor`,
  `textColor`, `accentColor`, and `modalBackgroundColor`; the slot fields and dot colors
  are read by the `'rewardUsers'` composition.
* **Accessibility survives your override.** The SDK keeps its semantic elements, so the
  sheet's accessible name and single tab stop per control are preserved. A custom element
  with no text content is announced with the copy the SDK resolved for that slot.

## Troubleshooting

**A color, font, or size is not applying.** Values go through an allow-list, and a
rejected value is dropped whole rather than half-applied: the sheet keeps its default and
nothing breaks. Accepted colors are hex values, the CSS color functions (including
wide-gamut ones such as `oklch()`, with no nested functions), named colors, and
`var(--your-property)` with a simple fallback. Font stacks accept unaccented family
names, quotes, and commas, so a family name with accented characters is rejected; to use
one, take the element or renderer form and style your own element with your own CSS;
the allow-list applies only to config values. Lengths need an explicit unit (`28px`,
`1.75rem`, `100%`); a number with no unit is rejected, except `0`. The rejection is logged as
`[Encore SDK] Ignoring unsupported … appearance value`, but SDK logging is off by
default: pass `logLevel: 'debug'` to
[`configure()`](/publishers/web/sdk-reference/configure) while integrating, or a dropped
value looks like a silent no-op.

**A renderer throws.** The sheet still presents, with the SDK's default element in that
slot, and a warning is logged at the same debug-gated level. A render bug costs you the
custom styling for one slot, never the presentation.

**A slot override shows no effect.** Check the placement's use case: the slot fields and
dot colors render only on `'rewardUsers'`. On `'reduceChurn'`, use the theme colors.

## Related

* [`placement()`](/publishers/web/sdk-reference/placement): the full `PlacementOptions` contract this config sits in.
* [Post-Purchase Placements](/publishers/web/guides/post-purchase-placements): the reward placement pattern end to end.
* [`show()`](/publishers/web/sdk-reference/show): the result statuses, including how to react to a claim.
* [`configure()`](/publishers/web/sdk-reference/configure): `logLevel: 'debug'` for the appearance warnings on this page.
* [`isClaimEnabled`](/publishers/web/sdk-reference/is-claim-enabled): turning the claim off, which applies to a custom claim button too.
