---
title: Kindly iOS SDK — Theming Reference
slug: sdk/ios-sdk-skill-theming
description: Complete reference for theming the Kindly iOS SDK. Use when customizing colors via setCustomTheme, building light/dark mode pairs, or working with the Theme protocol, KindlyTheme, or CustomTheme types. Lists every color slot, its API mapping, and which…
createdAt: 2026-11-04T11:40:17.860Z
---

Deep reference for every color slot in the Kindly iOS chat UI. Companion to [SKILL.md](https://docs.kindly.ai/sdk/ios-sdk-skill.md) — use this when an agent needs full color-by-color control over the widget.

All types listed here live in the `Kindly` module. Theme colors are `UIColor?`.

***

## Theme Resolution Order

For each color slot, the SDK picks the first non-`nil` value from this precedence chain:

1. **Custom theme** set via `KindlySDK.setCustomTheme(...)` (host app)
2. **API/server theme** loaded via `kindly:load` event (Kindly admin panel)
3. **Default `KindlyTheme`** baked into the SDK

Pass `nil` for any slot in `setCustomTheme(...)` to fall back to layer 2 or 3 for that slot only. This means partial overrides "just work".

***

## Applying a Theme

Two equivalent forms exposed on `KindlySDK`:

```swift
KindlySDK.setCustomTheme(
    background: .white,
    navBarBackground: .systemBlue,
    navBarText: .white,
    userMessageBackground: .systemBlue,
    userMessageText: .white
    // any unset color falls through to API/default
)
```

```swift
let theme = CustomTheme(
    background: .white,
    navBarBackground: .systemBlue,
    navBarText: .white,
    userMessageBackground: .systemBlue,
    userMessageText: .white
)
KindlySDK.setCustomTheme(theme)
```

Reset to the API/default theme:

```swift
KindlySDK.clearCustomTheme()
```

You can call `setCustomTheme` **before or after `start()`**; it does not require an active connection.

***

## Color Slot Reference

The `Theme` protocol defines two groups: **API-defined** (slots that mirror the Kindly admin panel theme) and **manually-defined** (mobile-only slots not covered by the web admin).

### API-defined slots

| Slot                    | Type       | API field                 | What it styles                                  |
| ----------------------- | ---------- | ------------------------- | ----------------------------------------------- |
| `background`            | `UIColor?` | `background`              | Chat view background                            |
| `navBarBackground`      | `UIColor?` | `header_background`       | Navigation bar / chat header background         |
| `navBarText`            | `UIColor?` | `header_text`             | Navigation bar title and icons                  |
| `botMessageBackground`  | `UIColor?` | `bot_message_background`  | Bot bubble background                           |
| `botMessageText`        | `UIColor?` | `bot_message_text`        | Bot bubble text                                 |
| `userMessageBackground` | `UIColor?` | `user_message_background` | User bubble background                          |
| `userMessageText`       | `UIColor?` | `user_message_text`       | User bubble text                                |
| `buttonBackground`      | `UIColor?` | `button_background`       | Quick-reply / dialogue button background        |
| `buttonText`            | `UIColor?` | `button_text`             | Button label text                               |
| `buttonOutline`         | `UIColor?` | —                         | Button border color (iOS-only render hint)      |
| `inputBackground`       | `UIColor?` | `input_background`        | Text input field background                     |
| `inputText`             | `UIColor?` | `input_text`              | Text input field text                           |
| `chatLogElements`       | `UIColor?` | —                         | Timestamps, dividers, "Bot is typing" indicator |

### Manually-defined slots (iOS-only)

These slots are not in the web admin panel and are only configurable from the mobile SDK.

| Slot                          | Type       | What it styles                                   |
| ----------------------------- | ---------- | ------------------------------------------------ |
| `buttonSelectedBackground`    | `UIColor?` | Background for buttons after they've been tapped |
| `defaultShadow`               | `UIColor?` | Shadow color used on bubbles, headers, etc.      |
| `inputCursor`                 | `UIColor?` | Caret color in the text input field              |
| `maintenanceHeaderBackground` | `UIColor?` | Background of the "maintenance mode" banner      |

***

## `KindlyTheme` — the built-in default

These are the fallbacks the SDK uses when no API or custom value is set:

| Slot                          | Default               |
| ----------------------------- | --------------------- |
| `background`                  | `#FFFFFF`             |
| `botMessageBackground`        | `#F1F4F8`             |
| `botMessageText`              | `#232F41`             |
| `buttonBackground`            | `#FFFFFF`             |
| `buttonOutline`               | `#000000` @ 8% alpha  |
| `buttonText`                  | `#000000`             |
| `chatLogElements`             | `#727272`             |
| `navBarBackground`            | `#445265`             |
| `navBarText`                  | `#FFFFFF`             |
| `inputBackground`             | `#F4F7FA`             |
| `inputText`                   | `#0C141F`             |
| `userMessageBackground`       | `#0069FF`             |
| `userMessageText`             | `#FFFFFF`             |
| `buttonSelectedBackground`    | `#000000` @ 18% alpha |
| `defaultShadow`               | `black` @ 8% alpha    |
| `inputCursor`                 | `#000000`             |
| `maintenanceHeaderBackground` | `#F9EF94`             |

***

## Worked Examples

### Brand-aligned light theme

```swift
let brand = UIColor(red: 0.10, green: 0.45, blue: 0.95, alpha: 1)
KindlySDK.setCustomTheme(
    background: .white,
    botMessageBackground: UIColor(white: 0.96, alpha: 1),
    botMessageText: .black,
    buttonBackground: brand,
    buttonText: .white,
    navBarBackground: brand,
    navBarText: .white,
    userMessageBackground: brand,
    userMessageText: .white
)
```

### Light/dark mode via `UIColor` dynamic providers

`UIColor` already supports trait-collection-based dynamic colors — use them so the theme follows system appearance automatically:

```swift
extension UIColor {
    static func dynamic(light: UIColor, dark: UIColor) -> UIColor {
        UIColor { trait in trait.userInterfaceStyle == .dark ? dark : light }
    }
}

KindlySDK.setCustomTheme(
    background: .dynamic(light: .white, dark: UIColor(white: 0.10, alpha: 1)),
    botMessageBackground: .dynamic(light: UIColor(white: 0.96, alpha: 1),
                                   dark: UIColor(white: 0.18, alpha: 1)),
    botMessageText: .dynamic(light: .black, dark: .white),
    userMessageBackground: .systemBlue,
    userMessageText: .white,
    inputBackground: .dynamic(light: UIColor(white: 0.95, alpha: 1),
                              dark: UIColor(white: 0.20, alpha: 1)),
    inputText: .dynamic(light: .black, dark: .white)
)
```

### Match an asset catalog color

```swift
KindlySDK.setCustomTheme(
    navBarBackground: UIColor(named: "BrandPrimary"),
    userMessageBackground: UIColor(named: "BrandPrimary"),
    userMessageText: UIColor(named: "BrandPrimaryOn")
)
```

### Implement a fully custom `Theme`

If you want every slot under your control instead of using `CustomTheme`'s fall-through behavior, implement the `Theme` protocol directly and pass it to `setCustomTheme(_:)`:

```swift
struct MyTheme: Theme {
    var background: UIColor? { .white }
    var navBarBackground: UIColor? { .systemBlue }
    // ... implement every slot ...
}

KindlySDK.setCustomTheme(MyTheme())
```

Slots you return as `nil` still fall through to the API/default chain.

### Read the active theme from a `kindly:load` event

The `KindlyEventStyle` payload (see [EVENTS.md](https://docs.kindly.ai/sdk/ios-sdk-skill-events.md)) carries the API-side hex strings. Use it to detect what the admin configured before deciding which slots to override:

```swift
KindlySDK.events
    .compactMap { event -> KindlyEventStyle? in
        if case .load(let settings) = event.detail { return settings.style }
        return nil
    }
    .sink { apiStyle in
        if apiStyle.headerBackground == nil {
            KindlySDK.setCustomTheme(navBarBackground: .systemBlue, navBarText: .white)
        }
    }
    .store(in: &cancellables)
```

***

## Notes & Gotchas

- `setCustomTheme` is safe to call **before** `start()` — the SDK stores the theme and applies it as soon as the UI mounts.
- Calling `setCustomTheme` while the chat UI is on screen takes effect immediately for most slots; some slots (e.g. `defaultShadow`) only re-render on next layout.
- `clearCustomTheme()` does **not** clear the API theme — only the host-app override. To force the built-in `KindlyTheme`, you'd need both no API style and no custom theme.
- `UIColor(hex:)` is an internal SDK extension — use the standard `UIColor(red:green:blue:alpha:)` or asset-catalog colors in host code.
