# Customization

Source: https://ceyo.ai/docs/signal/embedding-customization

### Theme and appearance

Pass partial theme tokens to match your product. Unspecified values use the defaults listed in the type reference.

```
theme: {
  colors: {
    primary: "#0F766E",
    primaryText: "#FFFFFF",
    background: "transparent",
    surface: "#FFFFFF",
    text: "#16181D",
    muted: "#6B7280",
    border: "#E5E7EB",
    success: "#047857",
    successSurface: "#ECFDF5",
    warning: "#B45309",
    warningSurface: "#FFFBEB",
    danger: "#B91C1C",
    dangerSurface: "#FEF2F2",
  },
  typography: {
    fontFamily: "'Inter', sans-serif",
    baseSize: "14px",
    smallSize: "12.5px",
    titleSize: "22px",
    titleWeight: "700",
    bodyWeight: "400",
  },
  shape: {
    radius: "10px",
    cardRadius: "10px",
    controlRadius: "10px",
    pillRadius: "999px",
  },
  spacing: {
    pagePadding: "20px",
    panelPadding: "16px",
    cardPadding: "16px 18px",
    gap: "12px",
    controlHeight: "34px",
  },
  density: "compact",
}
```

**Colors**

Brand, background, surface, text, border, and status colors.

**Typography**

Font family, text sizes, and title and body weights.

**Shape**

Base, card, control, and pill corner radii.

**Spacing**

Page, panel, card, gap, and control-height overrides. Explicit spacing values take precedence over the selected density.

**Density**

Use `compact` or `comfortable` spacing.

Product settings may set the heading and subtitle, project-name visibility, detail navigation, and single-location behavior. `showProjectAdminTabs` and `showLocationAdminTabs` reveal the applicable Settings and read-only Users tabs; despite their names, they never grant admin access. The session role still controls every action.

Theme and settings are mount-time options. To change either after mounting, call `unmount()` and mount the application again with the new values. Use `updateToken()` only for session replacement.

### Localization

Signal includes an immutable English catalog. To render another language, pass one active BCP 47 locale and a complete translated message catalog at mount time. The UI locale is independent from the language configured on a project or location.

```
// Start from the complete catalog and translate every value.
const nlMessages = Ceyo.translationTemplate();

Object.assign(nlMessages, {
  "app.defaultTitle": "AI-zichtbaarheid",
  "frame.title": "AI-zichtbaarheid",
  "common.saveChanges": "Wijzigingen opslaan",
  "common.cancel": "Annuleren",
  "navigation.overview": "Overzicht",
  "navigation.locations": "Locaties",
  "navigation.prompts": "Prompts",
  // Continue until every value in the returned catalog is translated.
});

const embed = Ceyo.mount("#signal-visibility", {
  sessionToken,
  localization: {
    locale: "nl-NL",
    messages: nlMessages,
  },
  onError(message) {
    reportEmbedError(message);
  },
});
```

**Complete template**

Call `Ceyo.translationTemplate()` to receive every required key with its English source value. Translate every value and preserve the keys.

**Validation**

The iframe checks the locale, required keys, non-empty values, and interpolation variables. English catalogs cannot be overridden.

**Safe fallback**

An invalid or incomplete catalog falls back to English and reports an `invalid_localization` message through `onError` and the browser console.

**Formatting**

Numbers and dates use the selected locale. Labels, placeholders, empty states, accessibility text, toasts, and errors come from the supplied catalog.

> **Mount-time option**
>
> Pass only the locale needed by the current user. To switch languages, unmount and mount again with the next locale and catalog.

### Client events

`onReady` fires once the component can be used. `onError` reports an error message. `onTokenExpired` applies a returned replacement token. Keep the returned handle when the host page needs to update or unmount the application later.

```
const embed = Ceyo.mount("#signal-visibility", {
  sessionToken,
  onReady() {
    document.querySelector("#signal-loading")?.remove();
  },
  onError(message) {
    reportEmbedError(message);
  },
  async onTokenExpired() {
    return refreshSession(currentSession.token, false);
  },
});

// The returned handle supports lifecycle changes outside callbacks.
embed.updateToken(freshToken);
embed.unmount();
```
