Theming

The chat interface resolves its colours from three layers. Understanding the order is the whole of this page.

Precedencelink

Remote / dashboard theme      highest priority
        overrides
Local / SDK theme
        overrides
Native / built-in theme       final fallback

The merchant's dashboard configuration wins, because the merchant owns how their support experience looks. Your local theme fills in whatever the dashboard did not set. The built-in theme fills in whatever neither of those set, so every colour always has a value.

Merging is per property, not per objectlink

Layers are merged token by token, not swapped wholesale. Setting one colour locally does not discard the rest of the theme.

Built-in:  headerBackground = purple,  chatBackground = off-white,  sentBubble = purple
Local:     headerBackground = green
Remote:    sentBubble = navy

Resolved:  headerBackground = green    (remote silent, local wins)
           sentBubble       = navy     (remote wins)
           chatBackground   = off-white (neither set it, built-in wins)

Nested structures (the background image block) merge the same way: a remote opacity does not erase a local image URL.

The colour tokenslink

Every token names a visible part of the interface, and the same names are used by the dashboard, the web widget and all four mobile SDKs.

chatBackground, headerBackground, headerPrimaryText, headerSecondaryText, headerIcon, closeButton, receivedBubble, receivedText, receivedTimestamp, sentBubble, sentText, sentTimestamp, systemMessageText, daySeparatorBackground, daySeparatorText, footerContainer, chatInputBackground, chatInputText, chatInputPlaceholder, chatInputBorder, sendButtonBackground, sendButtonIcon, attachmentButton, emojiButton, typingIndicatorBackground, typingIndicatorDot, unreadBadgeBackground, unreadBadgeText, scrollToBottomButtonBackground, scrollToBottomButtonIcon, onlineStatus, offlineStatus, error, success, warning.

Values are CSS colours on the web and hex strings (#RRGGBB or #AARRGGBB) on mobile. An unusable value is ignored and the next layer down applies - a bad colour never breaks the interface.

Light and darklink

mode picks which built-in palette forms the base. It follows the same precedence, with one rule: the dashboard's auto means "no opinion" and does not consume its turn, so your local dark still applies when the merchant has not pinned a mode. If nobody pins one, the viewer's system preference decides.

Remote mode = light | dark   ->  wins
Remote mode = auto (or none) ->  your local mode wins
Neither                      ->  the device / browser setting

Setting a local themelink

<!-- Web: script tag. A preset, or JSON for individual tokens. -->
<script src="https://widget.newinstance.cloud/embed.js"
  data-api-key="sk_live_abc123"
  data-theme='{"mode":"dark","colors":{"sentBubble":"#16a34a"}}'
></script>
NInstanceChat.init({
  apiKey: 'sk_live_abc123',
  theme: { mode: 'dark', colors: { sentBubble: '#16a34a' } },
});
LiveAndAiChatConfig(
    apiKey = "sk_live_abc123",
    theme = ChatThemeOverride(sentBubble = "#16A34A", mode = ChatThemeOverride.Mode.DARK),
)
try LiveAndAiChatConfig(
    apiKey: "sk_live_abc123",
    theme: ChatThemeOverride(mode: .dark, sentBubble: "#16A34A")
)
new NewinstanceChat({
  apiKey: 'sk_live_abc123',
  theme: { mode: 'dark', sentBubble: '#16A34A' },
});
NewinstanceChatConfig(
  apiKey: 'sk_live_abc123',
  theme: ChatThemeOverride(mode: 'dark', sentBubble: '#16A34A'),
);

The web widget also accepts 'dark' / 'light' as a bare string, and still accepts the older flat palette (primaryColor, backgroundColor, textColor, and so on), which is translated onto the tokens above.

When configuration cannot be fetchedlink

An invalid key, a disabled chat, an unreachable backend, a timeout, a malformed response - none of these leave the interface unstyled.

Remote configuration loads      ->  remote + local + built-in
Remote configuration fails      ->  local + built-in
No local theme either           ->  built-in

A previously valid local theme is never destroyed by a failed fetch, the interface never sits blank or half-painted, and no authentication or configuration detail is shown to the customer. The failure is reported to you on the error channel as CONFIG_FETCH_FAILED (see Authentication and identity -> Diagnosing problems), which is a warning, not a fatal error.

A partly usable remote response is used partly: the tokens that parsed are applied, and the ones that did not fall through to your local theme and then the built-in palette.

Authentication and theming are independentlink

They fail independently and are handled independently.

AuthenticationRemote themeResult
SucceedsLoadsChat works, fully themed by the dashboard
SucceedsFailsChat works, themed by your local theme and the built-in palette
Anonymous visitorLoadsChat works, fully themed - identity is irrelevant to theming
Failsn/aChat does not open, and you get a coded diagnostic

One resolution, one interfacelink

Theme precedence is resolved in exactly one place per SDK, and every part of the interface - header, message list, composer, day separators, buttons, badges - consumes that one resolved theme. There is no path by which the header can render the dashboard theme while the composer renders yours.

Theming — NewInstance Documentation