# Theming

The Agentforce iOS SDK lets you customize the colors, fonts, icons, and on-screen text of every UI component in the full-UI chat experience, so the agent matches your brand.

## Mobile Publisher Branding

:::note
If you're using **Agentforce Mobile Publisher**, you can configure branding through the Setup UI without writing code. The metadata-driven branding configuration supports colors, typography, icons, and display names with a live preview. This Setup UI is available starting with the Winter '27 (264) release.

To configure branding via Setup:

1. In Setup, search for **Agentforce Mobile Branding**
2. Select your branding scope and preview type
3. Customize colors, typography, icons, and display names
4. Click the preview (clickable) to see your changes in real-time

Changes made in Setup are applied automatically without requiring an app redeployment—only server metadata updates are needed.

For programmatic customization in SDK implementations, continue reading the sections below.
:::

:::note
This branding configuration applies to the Agentforce Mobile SDK and Agentforce Mobile Publisher. It doesn't apply to the standalone Enhanced Chat (MIAW) SDK.
:::

## Theme System

All theming types are public members of the `AgentforceSDK` module — `import AgentforceSDK` is enough; there's no separate theming namespace.

## Overview

The theming system is built on the Salesforce Cosmos design system and provides:

- **Color theming** — a complete palette with generic foundation tokens plus a token for every individual component.
- **Typography** — a theme-wide font family, plus per-style size, weight, and family overrides.
- **Icon theming** — replace built-in icons, with optional separate light and dark variants.
- **Display-name theming** — override user-visible strings such as titles, hints, and button labels.
- **Message width** — optionally cap the width of agent-authored text.
- **Light and dark mode** — automatic switching, or an explicitly forced mode.
- **Dynamic Type** — fonts scale with the user's text-size preference by default.
- **Reactive updates** — the theme manager is `@Observable`, so SwiftUI re-renders when values change.

### What you can customize

| Capability                                  | Type(s)                                          |
| ------------------------------------------- | ------------------------------------------------ |
| Colors (generic + per-component)            | `AgentforceColorToken`, `AgentforceColorPalette` |
| Font family (theme-wide)                    | `AgentforceThemeOverrides.fontFamilyName`        |
| Per-style typography (size, weight, family) | `AgentforceFontStyle`, `AgentforceFontOverride`  |
| Icons                                       | `AgentforceIconType`, `AgentforceIcon`           |
| Display-name strings                        | `AgentforceDisplayNameType`                      |
| Agent message maximum width                 | `AgentforceDimensions`, `AgentforceSizing`       |
| Theme mode (light, dark, system)            | `AgentforceThemeMode`                            |
| Dynamic Type on/off                         | `AgentforceDefaultThemeManager.dynamicFonts`     |

## Key types

| Type                                                         | Purpose                                                                                                                                                       |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AgentforceTheming`                                          | The single theming input you pass to `setTheming(_:)`: `.default`, `.overrides`, or `.customManager`.                                                         |
| `AgentforceThemeOverrides`                                   | A sparse bag of just the tokens you want to change (colors, fonts, icons, display names). The recommended way to theme.                                       |
| `AgentforceColorToken`                                       | Type-safe enum of every color token — use it instead of raw strings so typos become compile errors.                                                           |
| `AgentforceColorPalette`                                     | Protocol declaring every component color token. Refines `SharedUI.Colors`, which declares the generic tokens.                                                 |
| `AgentforceCosmosLightColors` / `AgentforceCosmosDarkColors` | The default light and dark palettes. `open` classes — subclass to build a reusable palette.                                                                   |
| `AgentforceThemeManager`                                     | The theme contract: `AgentforceTheme & AgentforceThemeControllable & Observable`.                                                                             |
| `AgentforceDefaultThemeManager`                              | The SDK's default manager. `open` — subclass and override `lightModeColors`, `darkModeColors`, `fixedSizeFonts`, `dynamicSizeFonts`, `icons`, `displayNames`. |
| `AgentforceThemeMode`                                        | `.light`, `.dark`, or `.system`.                                                                                                                              |
| `AgentforceFontStyle` / `AgentforceFontOverride`             | The 23 overridable text styles and the sparse size, weight, or family override applied to each.                                                               |
| `AgentforceIconType` / `AgentforceIcon`                      | The 23 customizable icon slots and the replacement image(s).                                                                                                  |
| `AgentforceDisplayNameType`                                  | The 46 customizable user-visible strings.                                                                                                                     |
| `AgentforceDimensions` / `AgentforceSizing`                  | Shared UI dimensions plus an optional maximum width for agent-authored text.                                                                                  |

## Supplying a theme

Provide your theme once, through `setTheming(_:)` on your agent configuration, using the `AgentforceTheming` enum:

```swift
public enum AgentforceTheming {
    case `default`
    case overrides(AgentforceThemeOverrides)
    case customManager(AgentforceThemeManager)
}
```

| Option                     | When to use                                                                                                       |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `.overrides` (recommended) | Tweak specific tokens and let everything else follow the SDK defaults. Any token you leave out keeps its default. |
| `.customManager`           | Own the entire theme end-to-end with a fully resolved manager.                                                    |
| `.default`                 | No in-code theme — the SDK default theme is used.                                                                 |

`setTheming(_:)` is available on `ServiceAgentConfiguration`, `EmployeeAgentConfiguration`, and `AgentforceConfiguration`. It returns a copy, so chain it like any other builder method.

The `AgentforceTheming` cases are mutually exclusive when you apply them with `setTheming(_:)`. You can't layer an overrides bag on top of your own theme manager. The raw `AgentforceConfiguration` initializer can accept both `themeManager:` and `themeOverrides:`, but a non-empty overrides bag wins and the manager is ignored for the branding merge. Use `setTheming(_:)` to make the choice explicit and avoid that ambiguity.

## Customize colors

Colors can be overridden token by token, or built up into a reusable palette.

### Override specific tokens (recommended)

Use the `.overrides` factory with the `AgentforceColorToken` enum. List only what you want to change; everything else keeps its default.

```swift
import AgentforceSDK
import SwiftUI

let config = ServiceAgentConfiguration(
    esDeveloperName: "MyAgent",
    organizationId: "00D123",
    serviceApiURL: "https://your-instance.salesforce-scrt.com"
)
.setTheming(.overrides(
    light: [
        .accent1: Color("AcmeAccentLight"),
        .surface1: .white,
    ],
    dark: [
        .accent1: Color("AcmeAccentDark"),
        .surface1: Color("AcmeSurfaceDark"),
    ]
))
```

Colors are plain SwiftUI `Color` values — the SDK doesn't expose a hex initializer, so define your brand colors as asset-catalog colors (which can also carry their own light/dark variants) or with `Color(red:green:blue:)`.

Most component tokens fall back to a generic token, so overriding a single generic token (like `.accent1`) re-skins everything that derives from it. To change just one component, override its specific token — for example `.carouselSurface` — without touching `.surface1`. Sixteen component tokens hold a literal rather than an alias and don't follow a generic override; see the [Color Token Reference](/docs/ai/agentforce-mobile-sdk/references/agentforce-mobile-sdk/agentforce-color-token-reference-ios.md) for the full list and every token's default value.

### Build a reusable palette by subclassing

iOS has no `withOverrides(...)` helper. Instead, subclass the default palettes — every token is an `open var`, and unoverridden tokens keep resolving through their generic default.

```swift
final class AcmeLightColors: AgentforceCosmosLightColors {
    override var accent1: Color { Color("AcmeOrange") }
    override var carouselSurface: Color { Color("AcmeOrangeTint") }
}

final class AcmeDarkColors: AgentforceCosmosDarkColors {
    override var accent1: Color { Color("AcmeOrangeDark") }
}

final class AcmeThemeManager: AgentforceDefaultThemeManager {
    override var lightModeColors: AgentforceColorPalette { AcmeLightColors() }
    override var darkModeColors: AgentforceColorPalette { AcmeDarkColors() }
}

// ...setTheming(.customManager(AcmeThemeManager(themeMode: .system)))
```

## Customize fonts

Fonts are overridden per style. Each `AgentforceFontOverride` field is optional — a `nil` field falls through to the SDK default, so you can change only a size or only a weight.

```swift
.setTheming(.overrides(
    fonts: [
        .titlesScale3Semibold: AgentforceFontOverride(size: 22, weight: .bold),
        .bodyScale1Regular: AgentforceFontOverride(fontFamilyName: "Inter-Light"),
    ],
    fontFamilyName: "Inter" // theme-wide family
))
```

`fontFamilyName` applies to every style that doesn't name its own family. A style-level `fontFamilyName` wins over it. Register custom fonts in your app target (`UIAppFonts` in `Info.plist`) and pass the exact PostScript name.

A few things worth knowing before you ship a custom family:

- **Names aren't validated.** An unregistered or misspelled name is passed straight to `Font.custom`, which substitutes a fallback face silently — no crash, no log, just the wrong typeface. Verify on device.
- **CSS generic names don't work here.** `"serif"`, `"monospace"`, and `"sans-serif"` aren't resolved as host values; pass a real family such as `"Georgia"` or `"Menlo"`.
- **`nil` means inherit, not reset.** Once a theme-wide family sets a field, passing `nil` at the style level keeps inheriting it. There's no way to opt a single style back to the system font.
- **Fall-through is per field, not per style.** Overriding just `size` on a style leaves that style's `weight` at the SDK default — overriding one field doesn't touch the others already set for that style.
- **Values are passed through as-is.** A host `size` isn't clamped, and `fontFamilyName: ""` counts as "set." Pass `nil` for "no change."

### Font styles and default sizes

| Style prefix          | Cases                   | Size  |
| --------------------- | ----------------------- | ----- |
| `bodyScaleNeg2*`      | Regular, Semibold, Bold | 10 pt |
| `bodyScaleNeg1*`      | Regular, Semibold, Bold | 12 pt |
| `bodyScaleBase*`      | Regular, Semibold, Bold | 13 pt |
| `bodyScale1*`         | Regular, Semibold, Bold | 14 pt |
| `bodyScale2*`         | Regular, Semibold, Bold | 16 pt |
| `titlesScale3*`       | Regular, Semibold       | 20 pt |
| `titlesScale4*`       | Regular, Semibold       | 24 pt |
| `titlesScale5Regular` | —                       | 28 pt |
| `titlesScale6Regular` | —                       | 32 pt |
| `displayScale7Light`  | —                       | 40 pt |
| `displayScale8Light`  | —                       | 48 pt |

### Dynamic Type

`AgentforceDefaultThemeManager(dynamicFonts:)` selects between two palettes: `AgentforceCosmosDynamicFonts` (default, scales with the user's text-size setting) and `AgentforceCosmosFixedFonts` (strictly fixed sizes). Leave it `true` unless you need pixel-fixed typography — Dynamic Type is an accessibility requirement (WCAG 1.4.4).

## Customize icons

Replace any built-in icon by mapping an `AgentforceIconType` to an `AgentforceIcon`. An icon can be a single image (used for both schemes) or separate light and dark variants.

```swift
.setTheming(.overrides(
    icons: [
        // Same image for light and dark:
        .actionSend: AgentforceIcon(Image("MySend")),
        // Separate variants:
        .statusError: AgentforceIcon(lightModeIcon: Image("ErrLight"), darkModeIcon: Image("ErrDark")),
    ]
))
```

**Icon slots.** Avatars: `aiAgent`, `humanAgent`. Input actions: `actionAttachFile`, `actionAttachPhoto`, `actionTakePhoto`, `actionSend`, `actionDownload`, `actionDelete`, `uiAdd`, `uiOptionsMenu`. Status: `statusError`, `statusFailed`, `statusWarning`. Menu and content: `iconForm`, `iconClearChat`, `iconSecureForm`, `iconWebLink`, `iconPDF`, `iconCopy`, `iconFeedbackPositive`, `iconFeedbackNegative`, `iconImagePlaceholder`, `iconClose`.

**Tinting.** Most icon slots are recolored by the SDK using a `*IconTint` color token and rendered as templates, so supply a single-color glyph for those. The `aiAgent` and `humanAgent` avatars always render full-color and keep the original colors of the image you supply. There's no host-settable "no tint" value on iOS.

Because several slots appear in more than one place, the tint token applied to your glyph depends on where it renders. `iconClose`, for instance, is tinted by `titleBarIconTint` in the title bar, `modalSheetItemIconTint` in a sheet, and `mediaViewerCloseIconTint` in the image viewer. Design replacement glyphs to read well against any of those.

**Known gap.** `uiOptionsMenu` is honored in the title bar and conversation-options menu, but the "more" glyph inside citations and toxicity notices still uses the built-in image and ignores the override.

## Customize display names (strings)

Override user-visible strings by mapping an `AgentforceDisplayNameType` to your text.

```swift
.setTheming(.overrides(
    displayNames: [
        .serviceAIAgentName: "Acme Assistant",
        .inputTextPlaceholder: "Ask me anything…",
        .preChatSubmitButton: "Connect with Support",
    ]
))
```

Two tokens carry a runtime value and must keep their placeholder: `typingIndicatorText` and `routingWaitResolvedMinutes` (both `%1$@`). An override with an unsafe or mismatched format string is rejected and the SDK's own template is used instead. An empty-string override is treated as "not customized" and falls back to the localized default.

**Accessibility (VoiceOver) copy is outside this customization surface** and isn't customizable through display names.

For all 46 slots, defaults, and locations, see [AgentforceDisplayNameType](/docs/ai/agentforce-mobile-sdk/references/agentforce-mobile-sdk/agentforce-display-name-type-ios.md).

## Set agent-message width

`messageTextMaximumWidth` caps agent-authored plain text, Markdown, and rich text. It doesn't cap user-authored messages. Because dimensions belong to a theme manager rather than the sparse overrides bag, set the value through a custom manager.

```swift
final class AcmeThemeManager: AgentforceDefaultThemeManager {
    override init(themeMode: AgentforceThemeMode = .system, dynamicFonts: Bool = true) {
        super.init(themeMode: themeMode, dynamicFonts: dynamicFonts)
        dimensions = AgentforceCosmosDimensions(messageTextMaximumWidth: 600)
    }
}

let config = existingConfiguration
    .setTheming(.customManager(AcmeThemeManager()))
```

The default is unbounded. `nil`, zero, and negative values are also treated as unbounded. See [AgentforceDimensions](/docs/ai/agentforce-mobile-sdk/references/agentforce-mobile-sdk/agentforce-dimensions-ios.md).

## Theme mode (light, dark, system)

Theme mode lives on the theme manager, not in the overrides bag:

```swift
let manager = AgentforceDefaultThemeManager(themeMode: .system, dynamicFonts: true)
let config = ServiceAgentConfiguration(/* ... */)
    .setTheming(.customManager(manager))
```

- `.system` — follow the device setting.
- `.light` / `.dark` — force a mode regardless of the device setting.

The SDK default is `.light`. Because mode isn't part of the overrides bag, use a custom manager when you need automatic light and dark mode with custom colors:

```swift
final class AcmeThemeManager: AgentforceDefaultThemeManager {
    override var lightModeColors: AgentforceColorPalette { AcmeLightColors() }
    override var darkModeColors: AgentforceColorPalette { AcmeDarkColors() }
}

let config = ServiceAgentConfiguration(/* ... */)
    .setTheming(.customManager(AcmeThemeManager(themeMode: .system)))
```

In `.system` mode, the theme reports light until the view first appears and syncs with the environment.

## Complete customization

Combine everything in a single overrides bag:

```swift
let config = ServiceAgentConfiguration(/* ... */)
    .setTheming(.overrides(
        light: [.accent1: Color("AcmeOrange")],
        dark: [.accent1: Color("AcmeOrangeDark")],
        icons: [.aiAgent: AgentforceIcon(Image("AcmeAvatar"))],
        displayNames: [.serviceAIAgentName: "Acme Assistant"],
        fonts: [.titlesScale3Semibold: AgentforceFontOverride(size: 22)],
        fontFamilyName: "Inter"
    ))
```

Or own the whole theme as a fully resolved manager:

```swift
final class AcmeThemeManager: AgentforceDefaultThemeManager {
    override var lightModeColors: AgentforceColorPalette { AcmeLightColors() }
    override var darkModeColors: AgentforceColorPalette { AcmeDarkColors() }
    override var dynamicSizeFonts: SharedUI.Fonts { AcmeFonts() }
    override var icons: [AgentforceIconType: AgentforceIcon] {
        [.aiAgent: AgentforceIcon(Image("AcmeAvatar"))]
    }
    override var displayNames: [AgentforceDisplayNameType: String] {
        [.serviceAIAgentName: "Acme Assistant"]
    }
}

// ...setTheming(.customManager(AcmeThemeManager(themeMode: .system)))
```

## Feature-flag-based theming

Roll out a custom theme gradually by choosing the theming input at runtime:

```swift
let theming: AgentforceTheming = customThemeEnabled
    ? .overrides(myOverrides)
    : .default
let config = ServiceAgentConfiguration(/* ... */).setTheming(theming)
```

## Server-driven branding

When server-driven branding is enabled, a Salesforce admin can centrally provide colors, fonts, icons, and display names for an org. The SDK fetches and validates that theme, then merges it beneath your in-code configuration. Your override wins for each token, followed by org branding, followed by the SDK default.

- `.overrides` preserves this per-token merge and is the recommended option.
- `.customManager` remains authoritative for colors, fonts, dimensions, shapes, and mode. Org branding fills only icon and display-name slots that the manager leaves unset.
- `.default` applies org branding over SDK defaults.

Theme mode, dimensions, and shapes aren't server brandable. Invalid or unknown values are dropped so the SDK can fall back safely. Your host app doesn’t construct the branding payload. No public branding enablement API is configured through theming.

## Migrate to `setTheming(_:)`

Earlier releases applied theming through theme-manager arguments and overloads. The recommended surface is the single `setTheming(_:)` call. Most older entry points are deprecated and emit a compiler warning, but the `themeManager:` argument on the configuration initializer is superseded, not deprecated.

| Older API                                                                         | Replacement                                                                                                                                                    |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configuration.withTheme(manager)` (Employee/Service) — deprecated                | `configuration.setTheming(.customManager(manager))`                                                                                                            |
| A `themeManager:` configuration initializer argument — superseded, not deprecated | Apply `setTheming(.customManager(manager))` after initialization, or use `.default`.                                                                           |
| `AgentforceClient(mode:viewProvider:themeManager:)`                               | Apply theming to the configuration, then initialize `AgentforceClient` without the theme-manager overload.                                                     |
| `AgentforceClient(credentialProvider:mode:viewProvider:themeManager:)`            | Pass authentication through the configuration, apply theming with `setTheming(_:)`, and then initialize `AgentforceClient` without the theme-manager overload. |
| Reading `configuration.themeManager` to change theming                            | Apply the new value with `setTheming(_:)`.                                                                                                                     |

If you already call `setTheming(_:)`, no migration is required. Sparse per-token overrides are available only through `setTheming(.overrides(...))`.

## Reading theme values in your own components

If you render custom components through `AgentforceViewProviding` and want them to match the theme, hold the same `AgentforceThemeManager` instance you handed to the SDK. Because it's `@Observable`, SwiftUI re-renders your views when the active scheme changes a value.

```swift
struct AcmeCard: View {
    let theme: AgentforceThemeManager

    var body: some View {
        Text("Hello")
            .font(theme.fonts.bodyScale1Regular.font)
            .foregroundStyle(theme.colors.onSurface1)
            .padding(theme.dimensions.space.spacing4)
            .background(theme.colors.surface1)
    }
}
```

Alongside `colors` and `fonts`, a theme manager exposes `dimensions` (`.space.spacing1`–`12`, `.size.sizing1`–`16`, border widths) and `shapes` (corner radii) from the shared Salesforce token families.

## Best practices

1. **Prefer `.overrides`.** Override only what you need and let the SDK supply the rest.
2. **Use generic tokens for broad changes, component tokens for surgical ones.** Change `.accent1` to re-brand everything accent-colored; change `.carouselSurface` to affect only the carousel.
3. **Always use the `AgentforceColorToken` enum** rather than raw strings — typos become compile errors.
4. **Set both light and dark.** Provide `light` and `dark` maps so your brand looks right in both modes; test with `.system`.
5. **Check contrast.** Ensure sufficient contrast for accessibility (WCAG AA) in both modes.
6. **Keep Dynamic Type on** unless you have a hard requirement for fixed sizing.
7. **Preserve string placeholders** (`%1$@`) when overriding the two runtime-format display names.
8. **Supply single-color glyphs for tinted icon slots;** the `aiAgent` and `humanAgent` avatars can be full-color.
9. **Don't customize accessibility copy through display names.** Accessibility copy is outside this customization surface.
10. **Verify custom fonts on device.** An unregistered family name fails silently and substitutes a fallback face.

## Promoting Embedded Service Branding with DevOps Center

When branding is saved for an Embedded Service configuration — for example, through the Mobile Publisher Branding Setup UI described above — the SDK creates a `BrandingSet` metadata record named:

`AFM_ESC_<EmbeddedServiceConfigDeveloperName>`

For example, an Embedded Service configuration with the developer name `MyMessagingConfig` creates the BrandingSet `AFM_ESC_MyMessagingConfig`.

`BrandingSet` doesn't have a parent metadata entity, so it can't be included in a change set. To promote this branding from a sandbox to production, use Salesforce DevOps Center instead:

1. Create or open a DevOps Center work item for the Embedded Service configuration.
2. On the work item's **Changes** tab, select **Add Components Manually**.
3. Select the **BrandingSet** metadata type and add the BrandingSet named `AFM_ESC_<EmbeddedServiceConfigDeveloperName>`.
4. Commit the work item to source control, complete any required review, and promote it through the configured pipeline to production.
5. Validate the branding in the target org after deployment.

When the Embedded Service configuration itself changes, include both the configuration and its associated BrandingSet in the same work item.

This limitation and workaround apply the same way regardless of mobile platform — the BrandingSet metadata is created server-side, independent of whether your app uses the Android, iOS, or React Native SDK.

For more information, see Salesforce's DevOps Center documentation on [manually adding metadata components](https://help.salesforce.com/s/articleView?id=platform.devops_center_work_item_add_metadata.htm\&language=en_US\&type=5), [committing work-item changes](https://help.salesforce.com/s/articleView?id=platform.devops_center_work_item_commit_changes.htm\&language=en_US\&type=5), and [promoting work items through a pipeline](https://help.salesforce.com/s/articleView?id=platform.devops_center_promotion_options.htm\&language=en_US\&type=5).

## Next steps

- **Color Token Reference**: See [Color Token Reference](/docs/ai/agentforce-mobile-sdk/references/agentforce-mobile-sdk/agentforce-color-token-reference-ios.md) for every token and its default light/dark value.
- **Theming Reference**: See [iOS Theming Overview](/docs/ai/agentforce-mobile-sdk/references/agentforce-mobile-sdk/theming-overview-ios.md) for typography, display names, icons, dimensions, server-driven branding, and migration guidance.
- **UI Components**: See [iOS UI Components](/docs/ai/agentforce-mobile-sdk/references/agentforce-mobile-sdk/ui-components-ios.md) for component theming.
- **Configuration**: See [AgentforceConfiguration](/docs/ai/agentforce-mobile-sdk/references/agentforce-mobile-sdk/configuration-overview-ios.md) for setup.
