cometchat-native-theming-19bb2e — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-native-theming-19bb2e (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Teaches Claude how to theme and localize the React Native UI Kit via CometChatThemeProvider + CometChatI18nProvider. No CSS — React Native uses a JS theme object instead. This skill covers color tokens, typography, light/dark modes, per-component style overrides, the useTheme() hook for custom views, and localization (18 built-in languages, device auto-detect, custom translation overrides) via useCometChatTranslation().
Read `cometchat-native-core` first (the wrapper chain that includes CometChatThemeProvider) before this skill. cometchat-native-components § 13 covers per-component style={} overrides, which are a sibling concern to theming.
Ground truth: docs/ui-kit/react-native/theme.mdx, colors.mdx, component-styling.mdx, message-bubble-styling.mdx, and packages/ChatUiKit/src/theme/type.ts (the canonical type definitions).
React Native has no CSS. Instead:
CometChatThemeProvider
↓ (provides theme via React Context)
every <CometChat*> component reads theme via internal useTheme()
↓
component's default styles merge with theme overrides → rendered stylesThe theme object you pass has two top-level keys for light/dark variants:
<CometChatThemeProvider
theme={{
mode: "light", // or "dark", or omit for OS-default
light: { color: { primary: "#F76808" } },
dark: { color: { primary: "#FF8A3D" } },
}}
>
<App />
</CometChatThemeProvider>CometChatThemeProvider — app-wide.So for a one-off color on a single component, use style={}. For a brand-wide change (primary color everywhere), use the theme.
Theme values are deeply merged with defaults — you only specify what you want to change:
theme={{
light: {
color: {
primary: "#F76808", // override just primary; everything else keeps defaults
},
typography: {
heading1: { fontWeight: "700" }, // override just heading1 weight
},
},
}}import { CometChatThemeProvider } from "@cometchat/chat-uikit-react-native";
<CometChatThemeProvider>
{/* children read the current system mode automatically */}
</CometChatThemeProvider><CometChatThemeProvider theme={{ mode: "light" }}>{/* ... */}</CometChatThemeProvider>
<CometChatThemeProvider theme={{ mode: "dark" }}>{/* ... */}</CometChatThemeProvider>CometChatThemeProvider is one of the four required wrappers — goes right above CometChatProvider, below SafeAreaProvider (see cometchat-native-core § 3):
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<CometChatThemeProvider theme={/* your theme */}>
<CometChatProvider appId={...} region={...} authKey={...}>
<YourApp />
</CometChatProvider>
</CometChatThemeProvider>
</SafeAreaProvider>
</GestureHandlerRootView>Without CometChatThemeProvider, components throw or fall back to minimal styles. Even if you don't customize anything, the wrapper is mandatory.
Every color is a hex string ("#F76808" — never "rgb(...)" or named colors).
| Token | Controls |
|---|---|
primary | Outgoing message bubbles, send button, active tabs, buttons |
extendedPrimary50–900 | Auto-derived shades of primary. Used for hover, pressed, subtle accents. Only override these if you need finer control — the auto-derivation is usually correct. |
| Token | Default (light) | Controls |
|---|---|---|
neutral50 | #FFFFFF | White/light surface, background1 default |
neutral100 | #FAFAFA | background2 default |
neutral200 | #F5F5F5 | background3 default |
neutral300 | #E8E8E8 | Incoming bubble default, borders |
neutral400 | #DCDCDC | Divider lines |
neutral500 | #A1A1A1 | Placeholder / muted text, iconSecondary default |
neutral600 | #727272 | textSecondary (timestamps, subtitles) |
neutral700 | #5B5B5B | Body text tier 3 |
neutral800 | #434343 | Headings default |
neutral900 | #141414 | textPrimary default, iconPrimary default |
| Token | Maps to (default) | Controls |
|---|---|---|
background1 | neutral50 | Main app background |
background2 | neutral100 | Sidebars, panels |
background3 | neutral200 | Nested panels, cards |
background4 | neutral300 | Additional surface |
| Token | Default | Controls |
|---|---|---|
textPrimary | neutral900 | Main body text |
textSecondary | neutral600 | Timestamps, subtitles |
textTertiary | neutral500 | Hints, placeholders |
textHighlight | primary | Links, mentions |
| Token | Default | Controls |
|---|---|---|
iconPrimary | neutral900 | Active / default icons |
iconSecondary | neutral500 | Inactive icons |
iconHighlight | primary | Action icons |
| Token | Default (light) | Controls |
|---|---|---|
info | #0B7BEA | Info callouts, links |
warning | #FFAB00 | Warning callouts |
success | #09C26F | Online indicator, success messages |
error | #F44649 | Error messages, validation |
| Token | Default | Controls |
|---|---|---|
sendBubbleBackground | primary | Outgoing bubble bg |
sendBubbleText | staticWhite (#FFFFFF) | Outgoing bubble text |
receiveBubbleBackground | neutral300 | Incoming bubble bg |
receiveBubbleText | neutral900 | Incoming bubble text |
| Token | Value | Controls |
|---|---|---|
staticBlack | #141414 | Fixed dark elements (overlays, opacity-based) |
staticWhite | #FFFFFF | Fixed light elements |
Don't pass mode — the provider reads the OS setting via useColorScheme() and re-renders on change. The user gets automatic dark mode when they flip the system setting.
<CometChatThemeProvider>{/* ... */}</CometChatThemeProvider><CometChatThemeProvider theme={{ mode: "dark" }}>{/* ... */}</CometChatThemeProvider>If your app has its own dark-mode switch (stored in user prefs or Redux), drive mode from that state:
const [darkMode, setDarkMode] = useState(false);
// ...
<CometChatThemeProvider theme={{ mode: darkMode ? "dark" : "light" }}>
<Switch value={darkMode} onValueChange={setDarkMode} />
<App />
</CometChatThemeProvider>The provider re-renders children and they pick up the new theme immediately.
Override the dark branch of the theme for a custom dark palette:
<CometChatThemeProvider
theme={{
light: { color: { primary: "#6852D6" } },
dark: { color: { primary: "#A594F3", background1: "#0B0B0F" } },
}}
>The theme has a typography block with tokens per role:
<CometChatThemeProvider
theme={{
light: {
typography: {
heading1: { fontFamily: "Inter-Bold", fontSize: 28, fontWeight: "700" },
heading2: { fontFamily: "Inter-SemiBold", fontSize: 20 },
body1: { fontFamily: "Inter-Regular", fontSize: 15 },
caption1: { fontFamily: "Inter-Regular", fontSize: 12 },
// ... etc
},
},
}}
>Common tokens: heading1, heading2, heading3, heading4, body1, body2, caption1, caption2, button1, button2. Each follows the RN TextStyle shape — fontFamily, fontSize, fontWeight, lineHeight, letterSpacing.
React Native font loading is NOT covered by the UI Kit — use your project's existing font system:
useFonts() from expo-font, load before rendering the providerios/<App>/Info.plist UIAppFonts + android/app/src/main/assets/fonts/ + run npx react-native-assetOnly reference a fontFamily in the theme once the font is actually loaded — otherwise iOS shows the system default and Android crashes.
Beyond color / typography, the theme has per-component style blocks for fine control. These sit inside the light / dark branches:
<CometChatThemeProvider
theme={{
light: {
// component-specific overrides
conversationStyles: {
containerStyle: { backgroundColor: "#FAFAFA" },
},
messageHeaderStyles: {
titleStyle: { fontSize: 18 },
},
messageListStyles: {
containerStyle: { padding: 8 },
sendBubbleStyle: {
backgroundColor: "#F76808",
textStyle: { color: "#FFFFFF" },
},
receiveBubbleStyle: {
backgroundColor: "#F5F5F5",
textStyle: { color: "#141414" },
},
},
messageComposerStyles: {
containerStyle: { backgroundColor: "#FFF", borderTopWidth: 1, borderTopColor: "#E8E8E8" },
},
},
}}
>Common component-style keys: conversationStyles, usersStyles, groupsStyles, groupMembersStyles, messageHeaderStyles, messageListStyles, messageComposerStyles, threadHeaderStyles, callButtonsStyles, callLogsStyles.
Each block has the same nested shape as the component's style prop (see cometchat-native-components § 13).
The exact list of style keys per component is authoritative in the kit's type file:
packages/ChatUiKit/src/theme/type.tsIf you're overriding a component style and the TypeScript compiler complains about an unknown key, check that file (or use useTheme() + autocomplete in your IDE).
<CometChatThemeProvider
theme={{ light: { color: { primary: "#FF6B35" } } }}
>
<App />
</CometChatThemeProvider>This single line changes the outgoing message bubble color, send button color, active tab indicator, and every primary accent in the UI. The extendedPrimary50–900 tints are auto-derived from primary.
<CometChatThemeProvider
theme={{
light: { color: { primary: "#FF6B35" } },
dark: { color: { primary: "#FF8F66", background1: "#1A1A1A" } },
}}
>
<App />
</CometChatThemeProvider><CometChatThemeProvider
theme={{
light: {
color: {
sendBubbleBackground: "#FF6B35",
sendBubbleText: "#FFFFFF",
receiveBubbleBackground: "#F0F0F0",
receiveBubbleText: "#1A1A1A",
},
},
}}
>Overriding the bubble tokens directly is cleaner than doing it via messageListStyles.sendBubbleStyle — the tokens apply consistently everywhere bubbles render (main list + thread panel + search results).
useFonts or bare npx react-native-asset)<CometChatThemeProvider
theme={{
light: {
typography: {
heading1: { fontFamily: "Inter-Bold" },
heading2: { fontFamily: "Inter-SemiBold" },
heading3: { fontFamily: "Inter-SemiBold" },
heading4: { fontFamily: "Inter-Medium" },
body1: { fontFamily: "Inter-Regular" },
body2: { fontFamily: "Inter-Regular" },
caption1: { fontFamily: "Inter-Regular" },
caption2: { fontFamily: "Inter-Regular" },
button1: { fontFamily: "Inter-SemiBold" },
button2: { fontFamily: "Inter-Medium" },
},
},
}}
>When you write a custom slot view (e.g. a TitleView on CometChatMessageHeader) and want your custom component to match the theme, use the useTheme() hook:
import { useTheme } from "@cometchat/chat-uikit-react-native";
function CustomTitle({ user }: any) {
const theme = useTheme();
return (
<Text style={{
color: theme.color.textPrimary,
fontFamily: theme.typography.heading3.fontFamily,
fontSize: theme.typography.heading3.fontSize,
}}>
{user.getName()}
</Text>
);
}
// Wire into a header:
<CometChatMessageHeader user={selectedUser} TitleView={(user) => <CustomTitle user={user} />} />This is how you write custom views that automatically follow dark mode — by reading tokens from useTheme() instead of hardcoding colors.
CometChatI18nProviderTheming and localization are separate concerns but ship together. If your users aren't all English-speaking, wire CometChatI18nProvider alongside CometChatThemeProvider. Every string rendered by the UI Kit (message-action labels, empty states, system messages, alerts) flows through the i18n layer.
The UI Kit ships translations for 18 languages out of the box:
de, en, es, fr, hi, hu, it, ja, ko, lt, ms, nl, pt, ru, sv, tr, zh, zh-twCometChatI18nProvider goes above CometChatThemeProvider (theme is a child of i18n, not the other way around):
import { CometChatI18nProvider, CometChatThemeProvider } from "@cometchat/chat-uikit-react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<CometChatI18nProvider>
<CometChatThemeProvider>
<CometChatProvider>
<YourApp />
</CometChatProvider>
</CometChatThemeProvider>
</CometChatI18nProvider>
</SafeAreaProvider>
</GestureHandlerRootView>With no props, CometChatI18nProvider reads the device locale via react-native-localize and picks the matching language if available, falling back to English. No setup needed beyond the wrapper.
Install `react-native-localize` (required peer dep for auto-detect):
npx expo install react-native-localize # Expo managed
# or
npm install react-native-localize && cd ios && pod install && cd .. # bare RNOverride the device default via selectedLanguage:
<CometChatI18nProvider selectedLanguage="ja">If the user's app has its own language preference (stored in settings / Redux / MMKV), drive selectedLanguage from that state. The provider re-renders children on change so strings update immediately.
Chain: `selectedLanguage` (if set + available) → device language (if `autoDetectLanguage=true`) → `fallbackLanguage` (default `'en'`).
<CometChatI18nProvider
selectedLanguage={user.preferredLanguage} // from your app state
autoDetectLanguage={true} // fall back to device language
fallbackLanguage="en" // final fallback
>If the user's preferred language isn't bundled AND there's no custom translation for it, the provider logs a warning and uses the fallback.
Pass a translations object to override specific keys in an existing locale, or add a brand-new locale the UI Kit doesn't ship:
const translations = {
en: {
// Override a built-in English string
"NO_MESSAGES_YET": "Say hello to start the conversation!",
"SENT": "Delivered",
},
th: {
// Add a new language — Thai
"NO_MESSAGES_YET": "ยังไม่มีข้อความ",
"SENT": "ส่งแล้ว",
// ...provide the full key set
},
};
<CometChatI18nProvider selectedLanguage="th" translations={translations}>The translation schema is a flat { KEY: "string" } map. Keys are screaming-snake-case (NO_MESSAGES_YET, MESSAGE_COMPOSER_MENTION_ALL, TRANSLATE, etc.). Full key list lives at packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/en/translation.json in the UI Kit source — grep for "KEY": there to find the exact key for a string you want to override.
When you write a custom slot view and need the current language (or want to translate your own strings using the same key set), use the useCometChatTranslation hook:
import { useCometChatTranslation } from "@cometchat/chat-uikit-react-native";
function CustomEmptyState() {
const { t, language } = useCometChatTranslation();
return <Text>{t("NO_MESSAGES_YET")}</Text>;
}The hook also exposes availableLanguages — useful for building a language-picker UI.
Calling useCometChatTranslation() from a component rendered OUTSIDE CometChatI18nProvider (common when a custom view mounts at the navigator root instead of inside the chat subtree) logs "useCometChatTranslation used outside provider, using fallback translations" and falls through to English. Check your wrapper chain — i18n must wrap every component that reads translations, which is the whole app tree in practice.
"rgb(...)", "rgba(...)", named colors, or hsl(...) will break the kit's internal color math (used to derive extendedPrimary). Use "#RRGGBB" or "#RRGGBBAA" (opacity via alpha).extendedPrimary50–900 are auto-derived from primary. Override them only if the auto-derivation doesn't match your brand's tints — and then override the full range, not just one level.style={} wins — the theme override becomes dead code. Pick one: theme for app-wide, style={} for one-offs. // Expo example
const [fontsLoaded] = useFonts({ "Inter-Bold": require("./assets/Inter-Bold.ttf") });
if (!fontsLoaded) return null;
return (
<CometChatThemeProvider theme={{ light: { typography: { heading1: { fontFamily: "Inter-Bold" } } } }}>
<App />
</CometChatThemeProvider>
);useTheme() from @cometchat/chat-uikit-react-native — that gives you the current theme (including any overrides you set). useColorScheme() only gives you the raw system mode.After changing the theme:
primary / sendBubbleBackgroundIf something looks unstyled or crashes:
packages/ChatUiKit/src/theme/type.ts| Skill | When to route |
|---|---|
cometchat-native-core | Always read first — init/login/provider wrapper chain |
cometchat-native-components | For per-component style={} prop (sibling concern to theming) |
cometchat-native-placement | Where CometChat components go |
cometchat-native-theming | This skill — app-wide color/typography/dark mode |
cometchat-native-customization | Custom slot views + useTheme() in your own components |
cometchat-native-expo-patterns | Expo font loading via expo-font |
cometchat-native-bare-patterns | Bare RN font loading via react-native-asset |
cometchat-native-troubleshooting | Colors not applying, dark mode not switching, font shows system default |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.