feat(in-app): add dark mode support for in-app messages#1131
feat(in-app): add dark mode support for in-app messages#1131BernardGatt wants to merge 7 commits into
Conversation
Introduce ColorScheme enum (.light, .dark, .auto) to control how in-app messages adapt to dark mode. AUTO (the default) reads the app's userInterfaceStyle and sends the resolved "light" or "dark" value to the Gist renderer via the existing postMessage protocol. Live theme changes are handled two ways: - System trait changes: GistView detects traitCollectionDidChange and notifies BaseMessageManager to push the update - Runtime setColorScheme() calls: BaseMessageManager subscribes to colorScheme state changes and pushes to the active EngineWeb Also exposes MessagingInApp.setColorScheme() for runtime changes that persist for the session. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sample app builds 📱Below you will find the list of the latest versions of the sample apps. |
SDK binary size reports 📊SDK binary size of this PRSDK binary size diff report vs. main branch |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1131 +/- ##
==========================================
- Coverage 69.26% 69.00% -0.26%
==========================================
Files 225 226 +1
Lines 11524 11702 +178
==========================================
+ Hits 7982 8075 +93
- Misses 3542 3627 +85 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Re-resolve colorScheme from the webView's trait collection at render time instead of using UITraitCollection.current during init. When showing a modal, copy the app window's overrideUserInterfaceStyle (or root VC's) to the modal window so in-app messages match the app's appearance when it forces light or dark mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WKWebView determines prefers-color-scheme from its window's trait collection, not the view's. Set overrideUserInterfaceStyle on the modal window based on the SDK's color scheme setting: - .light/.dark: force the window to match - .auto: inherit the app window's override if present The color scheme flows from the state store through ModalMessageManager to ModalViewManager, which applies it to the window before adding the view controller. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store the color scheme synchronously on MessagingInAppImplementation so it's immediately available regardless of async Task ordering. This fixes the race condition where setColorScheme() dispatches to the store but the message displays before the store processes it. Also applies overrideUserInterfaceStyle on the WKWebView itself for inline messages, where there's no separate modal window to override. ModalMessageManager subscribes to colorScheme state changes and updates the modal window when the store catches up, handling mid-display setColorScheme() calls. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace unused closure parameter with _ - Use keypath syntax for flatMap - Regenerate API docs with new ColorScheme public surface Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 59a15f4. Configure here.
…de on auto - Add .setColorScheme to the middleware allowlist so it isn't blocked before userId/anonymousId is set - Reset window overrideUserInterfaceStyle to .unspecified before inheriting app style in auto mode, preventing a stale forced override from persisting after switching back to auto Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| endpoint: currentConfiguration.endpoint, | ||
| messageId: currentConfiguration.messageId, | ||
| properties: currentConfiguration.properties, | ||
| colorScheme: colorSchemeMode.resolve(with: webView.traitCollection) |
There was a problem hiding this comment.
Should we reuse resolvedScheme here?
| import Foundation | ||
|
|
||
| class MessagingInAppImplementation: MessagingInAppInstance { | ||
| @Atomic static var currentColorScheme: ColorScheme = .auto |
There was a problem hiding this comment.
Can we avoid this and keep only one in InAppMessageState as single source of truth?

ios-darkmode.mov
Summary
ColorSchemeenum (.light,.dark,.auto) as a public API onMessagingInAppConfigOptions.auto(the default) resolves the current app dark mode state viaUITraitCollection.userInterfaceStyleand sends"light"or"dark"to the Gist renderer via the existingpostMessageprotocoloverrideUserInterfaceStyleon the modal UIWindow to control WKWebView's CSSprefers-color-scheme. For.auto, inherits the app window's override if present (supports apps with in-app dark mode toggles)overrideUserInterfaceStyleon the WKWebView directly, since inline views live in the app's own windowGistView.traitCollectionDidChangeand pushed to active WebViewsMessagingInApp.shared.setColorScheme()for runtime changes — stored synchronously to avoid async Task race conditions, then dispatched to the state store for consistencyinitializeaction →InAppMessageState) for consistency and testabilityCompanion to Android PR: customerio/customerio-android#761
Usage
Test plan
AUTOmode: in-app message follows system dark mode toggleAUTOmode with app window override (.overrideUserInterfaceStyle = .dark): message matches app, ignores systemAUTOmode with Info.plistUIUserInterfaceStyle = Dark: message renders dark, ignores system.darkvia builder: message always dark regardless of system.darkvia runtimesetColorScheme(): message always dark regardless of systemNote
Low Risk
Additive UI/theming on the in-app display path; default
.autokeeps existing appearance unless integrators opt in.Overview
Adds light / dark / auto theming for in-app messages via a new public
ColorSchemeAPI on config (MessagingInAppConfigBuilder.setColorScheme) and at runtime (MessagingInApp.shared.setColorScheme), with wrapper SDK dictionary parsing forcolorScheme.The chosen mode is stored in
InAppMessageStateand resolved to"light"or"dark"for the Gist renderer (initialEngineWebConfigurationplus liveupdateColorSchemepostMessage). WKWebViewoverrideUserInterfaceStyleis applied for inline content; modal overlays set the same on a dedicatedUIWindow(with.autoinheriting the app window when needed). Live updates react to store changes andGistView.traitCollectionDidChangewhen system or app appearance changes.Reviewed by Cursor Bugbot for commit 79a754d. Bugbot is set up for automated code reviews on this repo. Configure here.