Skip to main content
Version: 5.9.0

Components

Components are the shared building blocks of a theme (navigation bar, input field, player, and so on). A single component is reused across screens and configured centrally through ChatComponents; in individual flows, its style can be overridden per-screen.

Configuration example:

// Create components with custom colors
let components = ChatComponents(colors: myColors, typography: myTypography)

// Override parameters of components shared across the SDK
components.navigationBarStyle.titleTextStyle.color = .white
// The cancel button in the search bar is the UIKit UISearchBar.cancelButton; because
// of UISearchBar API limitations, only `color.normal` applies.
// `tintColor` and other ButtonStyle properties for this element are ignored —
// see design-system/known_issues.md.
components.searchBarStyle.cancelButtonStyle.color.normal = .white

// Create a theme from components
let theme = ChatTheme(components: components)

or through the build constructor:

let components = ChatComponents.build { components in
components.searchBarStyle.cancelButtonStyle.color.normal = .black
components.loadingChatStyle.indicatorStyle.backgroundColor = .systemGray3
components.loadingChatStyle.indicatorStyle.cornerRadius = 20.0
components.audioPlayerStyle.playButtonStyle.image = ChatImage(system: "play.fill", tintColor: .red)
components.audioPlayerStyle.pauseButtonStyle.image = ChatImage(system: "pause.fill", tintColor: .green)
components.audioPlayerStyle.progressViewStyle.color = .black
components.audioPlayerStyle.progressViewStyle.backgroundColor = .yellow
}

Constructor parameters (all optional, with default values):

  • images: ChatImages — images used by the theme.
  • colors: ChatColors — color palette.
  • typography: ChatTypography — font settings.

Chat component styles

NameDescription
navigationBarStyleNavigation bar: title, buttons, background.
searchBarStyleSearch bar inside navigationBarStyle.
loadingIndicatorStyleLoading indicator (spinner).
loadingChatStyleLoading screen shown when the chat opens.
chatPlaceholderStyleEmpty chat placeholder.
errorPlaceholderStyleError screen with a "Retry" button.
inputTextStyleMessage input text field.
inputSearchTextStyleText field in the search bar.
audioPlayerStyleVoice message player.
inputViewStyleBottom chat panel: input field, send and attach buttons.
chatMenuStyleFile / photo / camera picker menu.
photoPickerStyle is deprecated

photoPickerStyle is deprecated; use chatMenuStyle.

Screen anatomy

The screenshots below map visual areas of the iOS chat to fields in ChatComponents, chatFlow, and searchFlow. Captured from the demo app (default light theme). The field structure is the same for any theme.

Where to set a style

A style is set globally through components.X (all flows) or per-screen through theme.flows.chatFlow.X / theme.flows.searchFlow.X (one screen). More on priority in the Design system.

Main chat screen

Main chat screen with messages and quick replies
ElementPath in themesStyle class
Navigation "Back" buttonchatFlow.navigationBarStyle.backButtonColorNavigationBarStyle
Chat title (operator name)chatFlow.navigationBarStyle.titleTextStyleNavigationBarStyle, ChatTextStyle
Subtitle (operator role)chatFlow.navigationBarStyle.subtitleTextStyleNavigationBarStyle, ChatTextStyle
Search icon in the navigation barchatFlow.navigationBarStyle.searchButtonStyleNavigationBarStyle, NavigationBarButtonStyle
Chat screen backgroundchatFlow.backgroundColorChatFlow
Date separator ("today")chatFlow.systemMessages.dateMessageStyleChatSystemMessagesStyles, ChatTextStyle
Outgoing message bubble colorchatFlow.outcomeMessages.bubbleTintColorChatMessagesStyles
Outgoing message textchatFlow.outcomeMessages.textMessageStyle.textStyleTextChatMessageStyle, ChatTextStyle
Status icons (sent / delivered / read)ChatMessageStyle.pendingStatusImage / deliveredStatusImage / readStatusImageChatMessageStyle
System message ("You will be answered by…")chatFlow.systemMessagesChatSystemMessagesStyles
Quick reply buttonsSee QuickReplyStyle (Styles)QuickReplyStyle
Message input fieldchatFlow.inputViewStyle.inputTextStyleChatInputStyle, ChatInputTextStyle
Attachment button (clip)chatFlow.inputViewStyle.attachButtonStyleChatInputStyle, IconButtonStyle
Microphone button (inactive state)chatFlow.inputViewStyle.voiceButtonStyleChatInputStyle, IconButtonStyle
Send buttonchatFlow.inputViewStyle.sendButtonStyleChatInputStyle, IconButtonStyle
Quick reply buttonschatFlow.quickRepliesStyleQuickReplyStyle

Message context menu

Context menu on long-press of a message

The menu appears on a long press on a message; the set of actions ("Quote", "Copy", "Delete") depends on the message state and server configuration.

ElementPath in themesStyle class
Selected message bubblechatFlow.outcomeMessages / incomeMessagesChatMessagesStyles
Menu actions ("Quote", etc.)Not configurable through the SDK — rendered through system UIKit (UIMenu/UIMenuController)
Opening the search screen
ElementPath in themesStyle class
Search barsearchFlow.navigationBarStyle.searchBarStyleSearchBarStyle
Text and cursor in the search fieldcomponents.inputSearchTextStyleChatInputTextStyle
"Cancel" buttonsearchFlow.navigationBarStyle.searchBarStyle.cancelButtonStyle.color.normalChatButtonColor
Search screen backgroundsearchFlow.backgroundColorChatUserFlow
"Nothing found" illustrationChatImages.searchNotFoundPlaceholderImageChatImages
"Nothing found" textsearchFlow.notFoundTextStyleChatTextStyle

Search results

List of found messages with match highlights
ElementPath in themesStyle class
Match highlight in the textsearchFlow.searchMessageStyle.matchTextStyleSearchChatMessageStyle, ChatTextStyle
Result row style (name, date, preview)searchFlow.searchMessageStyleSearchChatMessageStyle
Clear search field button (×)searchFlow.navigationBarStyle.searchBarStyleSearchBarStyle

Empty chat state

Chat without messages — welcome screen
ElementPath in themesStyle class
Welcome illustrationChatImages.emptyChatPlaceholderImageChatImages
"Welcome…" titlecomponents.chatPlaceholderStyle.titleTextStyleChatPlaceholderStyle, ChatTextStyle
Subtitle (description)components.chatPlaceholderStyle.subtitleTextStyleChatPlaceholderStyle, ChatTextStyle
Illustration (through ChatPlaceholderStyle)components.chatPlaceholderStyle.imageChatPlaceholderStyle, ChatImage
Screen backgroundchatFlow.backgroundColorChatFlow

Chat loading

Full-screen loader during chat initialization
ElementPath in themesStyle class
Spinner indicatorcomponents.loadingChatStyle.indicatorStyleChatLoadingStyle, LoadingIndicatorStyle
"Loading" textcomponents.loadingChatStyle.textStyleChatLoadingStyle, ChatTextStyle
Loader backgroundcomponents.loadingChatStyle.indicatorStyle.backgroundColorLoadingIndicatorStyle

Chat initialization error

Initialization error screen with a Retry button
ElementPath in themesStyle class
Default error illustrationcomponents.errorPlaceholderStyle.defaultErrorImageChatPlaceholderErrorStyle, ChatImage
Server error illustration (HTTP 4xx/5xx)components.errorPlaceholderStyle.serverErrorImageChatPlaceholderErrorStyle, ChatImage
Error titlecomponents.errorPlaceholderStyle.titleTextStyleChatPlaceholderErrorStyle, ChatTextStyle
Error subtitlecomponents.errorPlaceholderStyle.subtitleTextStyleChatPlaceholderErrorStyle, ChatTextStyle
"Retry" button — background colorcomponents.errorPlaceholderStyle.repeatButtonStyle.color.normalTextButtonStyle, ChatButtonColor
"Retry" button — text colorcomponents.errorPlaceholderStyle.repeatButtonStyle.titleTextStyle.colorTextButtonStyle, ChatTextStyle

Voice message recording

Input panel in voice recording mode
ElementPath in themesStyle class
Microphone button (recording trigger)chatFlow.inputViewStyle.voiceButtonStyleChatInputStyle, IconButtonStyle
Recording style as a whole (cancel, timer, progress)chatFlow.inputViewStyle.voiceRecordStyleChatInputStyle, ChatInputVoiceStyle
Recording cancel button (×) — inside voice modechatFlow.inputViewStyle.voiceRecordStyle (see Styles)ChatInputVoiceStyle

Dialog completion and rating

End of the dialog with an invitation to rate quality
ElementPath in themesStyle class
Operator avatar on the incoming messagechatFlow.incomeMessages.showAvatar (on/off)ChatMessagesStyles
Style of the "Dialog ended…" system messagechatFlow.systemMessages.dateMessageStyle (for neutral system text)ChatSystemMessagesStyles, ChatTextStyle
Rating invitation titlechatFlow.systemMessages.surveyMessageStyle.questionTitleTextStyleSurveyChatMessageStyle, ChatTextStyle
Invitation subtitlechatFlow.systemMessages.surveyMessageStyle.questionSubtitleTextStyleSurveyChatMessageStyle, ChatTextStyle
Star icon (selected / unselected)chatFlow.systemMessages.surveyMessageStyle.voteIconSurveyChatMessageStyle, ChatSurveyIcon
Star color (selected / unselected)voteSelectedColor / voteUnselectedColorSurveyChatMessageStyle, UIColor
Like/dislike icons (for a binary survey)voteLikeIcon / voteDislikeIconSurveyChatMessageStyle, ChatSurveyIcon
Don't see an element in the anatomy?

The full property list is in Styles and Flows. If a field is not in the SDK public API, it is not configurable — see Known issues.

Customization examples for typical elements

Text input field

components.inputTextStyle.apply { style in
style.cursorColor = UIColor(named: "BrandColor") ?? .systemBlue // cursor
style.textStyle.color = .label // text color
style.backgroundColor = .systemBackground
}

Or only on the chat screen (without affecting search):

let customInput = ChatInputTextStyle.build(with: components) { style in
style.cursorColor = UIColor(named: "BrandColor") ?? .systemBlue
}
theme.flows.chatFlow.inputViewStyle.inputTextStyle = customInput
components.navigationBarStyle.apply { style in
style.titleTextStyle.font = .systemFont(ofSize: 17, weight: .semibold)
style.titleTextStyle.color = .white
style.backgroundColor = UIColor(named: "BrandColor") ?? .systemBlue
}
Conditional property application

The behavior depends on the isConfigurable flag in NavigationBarStyle:

  • isConfigurable=true — custom title: all titleTextStyle properties (font + color) and subtitleTextStyle apply.
  • isConfigurable=false — standard UIKit nav bar: only titleTextStyle.color applies (through titleTextAttributes). The titleTextStyle.font and the entire subtitleTextStyle are ignored.

See also Known issues → Conditionally applied parameters.

Message bubble color

// Incoming (from the operator)
theme.flows.chatFlow.incomeMessages.bubbleTintColor = UIColor(named: "BubbleIncoming") ?? .systemGray5
// Outgoing (from the client)
theme.flows.chatFlow.outcomeMessages.bubbleTintColor = UIColor(named: "BrandColor") ?? .systemBlue
// Bubble text color
theme.flows.chatFlow.incomeMessages.textMessageStyle.textStyle.color = .label
theme.flows.chatFlow.outcomeMessages.textMessageStyle.textStyle.color = .white
Error color applies to outgoing messages only

The bubbleErrorColor property in ChatMessagesStyles applies only to outgoing messages (from the client). Incoming messages do not show the error in the bubble color.

Send button

let customInput = ChatInputStyle.build(with: components) { style in
// The icon color is set in the ChatImage itself; background/state colors — through `color`.
style.sendButtonStyle.image = ChatImage(system: "paperplane.fill", tintColor: .white)
style.sendButtonStyle.color.normal = UIColor(named: "BrandColor") ?? .systemBlue
style.sendButtonStyle.color.disabled = .systemGray3
}
theme.flows.chatFlow.inputViewStyle = customInput

Button colors (ChatButtonColor)

Sets the button color in three states: normal, highlighted, disabled. Used in ButtonStyle, IconButtonStyle, NavigationBarButtonStyle, TextButtonStyle.

let buttonColor = ChatButtonColor(
normal: .systemBlue, // normal state
highlighted: .systemBlue.withAlphaComponent(0.7), // when pressed
disabled: .systemGray3 // disabled button
)

All three states are required. By default they are derived from the ChatColors palette:

StateDefault color
normalcolors.link
highlightedcolors.linkLight
disabledcolors.disabled

You can apply custom colors either at construction time or through properties:

buttonColor.normal = UIColor(named: "BrandColor") ?? .systemBlue
components.searchBarStyle.apply { style in
// For the cancel button in the search bar, only color.normal applies;
// the `tintColor` of SearchBarStyle itself and other ButtonStyle properties
// for this element are ignored (see known_issues.md).
style.cancelButtonStyle.color.normal = .white
// The cursor and text of the search field are configured through inputSearchTextStyle
// (see the "Search input field" section below).
}

Loading indicator

components.loadingIndicatorStyle.apply { style in
style.indicatorColor = UIColor(named: "BrandColor") ?? .systemBlue
}

Chat loading screen

components.loadingChatStyle.apply { style in
style.indicatorStyle.backgroundColor = .systemGray6
style.indicatorStyle.cornerRadius = 16.0
}

Empty chat state

// Text when the chat is empty
components.chatPlaceholderStyle.apply { style in
style.titleTextStyle.color = .secondaryLabel
style.titleTextStyle.font = .systemFont(ofSize: 15, weight: .regular)
}

// Replace the illustration (through the images token)
let images = ChatImages()
images.emptyChatPlaceholderImage = ChatImage(named: "EmptyChat", bundle: .main)

Loading error

components.errorPlaceholderStyle.apply { style in
style.titleTextStyle.color = .label
// "Retry" button background color — through ChatButtonColor (state-aware).
// The type of repeatButtonStyle is TextButtonStyle, and the UI reads color.normal,
// not tintColor.
style.repeatButtonStyle.color.normal = UIColor(named: "BrandColor") ?? .systemBlue
style.repeatButtonStyle.titleTextStyle.color = .white
}

// Replace the error illustration (through the token)
images.errorPlaceholderImage = ChatImage(named: "ErrorIllustration", bundle: .main)

Audio player

components.audioPlayerStyle.apply { style in
style.playButtonStyle.image = ChatImage(system: "play.fill", tintColor: UIColor(named: "BrandColor") ?? .systemBlue)
style.pauseButtonStyle.image = ChatImage(system: "pause.fill", tintColor: UIColor(named: "BrandColor") ?? .systemBlue)
style.progressViewStyle.color = UIColor(named: "BrandColor") ?? .systemBlue
style.progressViewStyle.backgroundColor = .systemGray5
}

Search input field

components.inputSearchTextStyle.apply { style in
style.cursorColor = UIColor(named: "BrandColor") ?? .systemBlue
style.textStyle.color = .label
}

Figma and SDK mapping

On the Components page in Figma, components are organized into 8 sections. Below is the mapping between Figma sections and SDK styles:

Top Navigation component
Bottom Input states
Figma componentVariantsSDK style
Top NavigationiOS/Android × Light/DarknavigationBarStyle (NavigationBarStyle)
Bottom InputEmpty, Filled, Voice Record, Voice Recorded, Search NavigationinputViewStyle (ChatInputStyle)
Cite BottomMessage / Photo / Docs × Light/DarkchatFlow.inputViewStyle.quoteStyle (ChatInputQuoteStyle)
Scroll ButtonLight / DarkchatFlow.scrollToTopUnreadMessagesButtonStyle and chatFlow.scrollToBottomUnreadMessagesButtonStyle (ScrollToMessageButtonStyle)
BadgeSingle / Numerous × Light/DarkThe visual style of the badge is not publicly configurable in the SDK; the unread count is available through getUnreadMessagesCount and the delegate

Status (Figma)

Message Status variants
Figma componentVariantsSDK style
Message statusRead, Delivered, Sending, Error, Edited (Client/Agent, with/without backplate)ChatMessageStyle: readStatusImage, deliveredStatusImage, pendingStatusImage, editedStatusImage, errorStatusColor

Button (Figma)

Button types and states
Button type (Figma)StatesSDK style
BotNormal, Active, Pressed, Disable, Loading, ErrorQuickReplyStyle
TextNormal, Active, Pressed, Disable, Loading, ErrorTextButtonStyle
IconNormal, Active, Pressed, Disable, Loading, ErrorIconButtonStyle
ImageNormal, Active, Pressed, Disable, Loading, ErrorButtonStyle
SelectionNormal, Pressed, DisableQuickReplyStyle

Bubbles (Figma)

Message Base anatomy (Agent and Client)
Figma componentVariantsSDK style
Message Base (Agent)Normal, MultiContent, Deleted, Search agent, ErrorchatFlow.incomeMessages (ChatMessagesStyles)
Message Base (Client)Normal, MultiContent, ErrorchatFlow.outcomeMessages (ChatMessagesStyles)
System messagesSnooze, date, survey, operator connectedchatFlow.systemMessages (ChatSystemMessagesStyles)

Bubble anatomy in Figma: Avatar → Tail → Content (text/media) → Status. Configured through:

  • ChatMessagesStyles.showAvatar — show the avatar
  • ChatMessageStyle.messageBubbleEndMargin — margin from the screen edge
  • ChatMessageStyle.containerLeftOffset / containerRightOffset — container offsets

Figma states → SDK properties

Figma stateDescriptionSDK property
NormalRegular messagetextMessageStyle, imageMessageStyle, bubbleTintColor
Typing"Typing" indicatorStyle not publicly configurable
SnoozeOperator temporarily unavailablechatFlow.systemMessages.scheduleMessageStyle (ScheduleChatMessageStyle)
Search agentSearch result with highlightingsearchFlow.searchMessageStyle (SearchChatMessageStyle)
ErrorSend errorChatMessageStyle.errorStatusColor, errorTextStyle, errorInfoButton
DeletedDeleted messageChatMessagesStyles.deletedTextStyle (ChatTextStyle)

Input Field (Figma)

Figma componentVariantsSDK style
Input fieldEmpty, partially filled, completely filledinputTextStyle (ChatInputTextStyle)

OS UI (Figma)

Figma componentDescriptionSDK style
iOS Select FileSystem file pickerchatMenuStyle (ChatMenuStyle)
GalleryPhoto gallerychatMenuStyle
Select ImageImage selectionchatMenuStyle

Content (Figma)

Figma componentDescriptionSDK style
ContentMessage content types: text, image, file, audio, survey, quote, OG preview, and othersTextChatMessageStyle, ImageChatMessageStyle, FileChatMessageStyle, AudioChatMessageStyle, SurveyChatMessageStyle, QuoteStyle, OpenGraphViewStyle

Illustration (Figma)

Figma componentVariantsSDK style
IllustrationWelcome, Search, Error, and othersemptyChatPlaceholderImage, errorPlaceholderImage, searchNotFoundPlaceholderImage (in ChatImages)

Attachment menu (chatMenuStyle)

chatMenuStyle controls the appearance of the file, photo, and camera picker menu — the action sheet that appears when the attachment button is pressed.

Available properties

The menu is implemented through the system UIAlertController, so only three properties have a visual effect:

PropertyTypeDescriptionDefault
backgroundColorUIColorMenu sheet backgroundcolors.background
titleTextStyle.colorUIColorText and icon color of buttons (forwarded to alert.view.tintColor)colors.link
maximumImagesCountUInt8Maximum number of selectable images10
tintColor, cornerRadius, titleTextStyle.font

These fields are present on ChatMenuStyle (inherited from ChatStyle / ChatTextStyle), but because of the system UIAlertController implementation, they have no visual effect. Set the color through titleTextStyle.color; corner rounding is managed by the system.

Configuration example

// Globally through ChatComponents (applied across all screens)
components.chatMenuStyle.apply { style in
style.backgroundColor = UIColor(named: "BackgroundPrimary") ?? .white
style.titleTextStyle.color = UIColor(named: "BrandColor") ?? .systemBlue
}

Or through build to create a new instance:

let menuStyle = ChatMenuStyle.build(with: components) { style in
style.backgroundColor = .white
style.titleTextStyle.color = .systemBlue
style.maximumImagesCount = 5
}
components.chatMenuStyle = menuStyle

Chat screen only

If you need to change the menu style only in the chat (without affecting other screens):

// Access through chatFlow (the property is called addFileMenuStyle)
theme.flows.chatFlow.addFileMenuStyle.apply { style in
style.backgroundColor = UIColor(named: "BackgroundPrimary") ?? .white
style.titleTextStyle.color = UIColor(named: "BrandColor") ?? .systemBlue
}
Relationship with the global chatMenuStyle

When ChatFlow is created, the addFileMenuStyle property is initialized with a reference to the same instance held in components.chatMenuStyle at the moment the theme is initialized. After that they are independent: if you later run components.chatMenuStyle = newStyle, it does not affect chatFlow.addFileMenuStyle — the previous object remains there.

To change the menu only in chatFlow without a global effect, create a new instance through build and assign it directly: theme.flows.chatFlow.addFileMenuStyle = myMenuStyle.