
Cometchat Native Theming
- 6 installs
- 70 repo stars
- Updated June 23, 2026
- cometchat/cometchat-skills
Themes and localizes a CometChat React Native app via CometChatThemeProvider and CometChatI18nProvider using JS theme objects for color, typography, and dark mode.
About
Configures CometChat React Native appearance and localization through theme and i18n providers, replacing CSS variables with a JS theme object. A developer uses it to apply brand colors, dark mode, and translations across chat components.
- Color tokens, typography, dark mode, and per-component style overrides
- 18 built-in languages plus custom translations via CometChatI18nProvider
Cometchat Native Theming by the numbers
- 6 all-time installs (skills.sh)
- Ranked #1,500 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cometchat/cometchat-skills --skill cometchat-native-themingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 70 |
| Last updated | June 23, 2026 |
| Repository | cometchat/cometchat-skills ↗ |
What it does
Themes and localizes a CometChat React Native app via CometChatThemeProvider and CometChatI18nProvider using JS theme objects for color, typography, and dark mode.
Files
Purpose
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).
---
1. How theming works (no CSS — JS theme object)
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>Style precedence (highest to lowest)
1. Component `style={}` prop — wins always. Per-component tweak, overrides everything. 2. Custom theme via CometChatThemeProvider — app-wide. 3. Default theme — the UI Kit's built-in palette.
So for a one-off color on a single component, use style={}. For a brand-wide change (primary color everywhere), use the theme.
Deep merge
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
},
},
}}---
2. The CometChatThemeProvider
Minimum setup — follow system light/dark
import { CometChatThemeProvider } from "@cometchat/chat-uikit-react-native";
<CometChatThemeProvider>
{/* children read the current system mode automatically */}
</CometChatThemeProvider>Force a mode
<CometChatThemeProvider theme={{ mode: "light" }}>{/* ... */}</CometChatThemeProvider>
<CometChatThemeProvider theme={{ mode: "dark" }}>{/* ... */}</CometChatThemeProvider>App-controlled theme toggle (wire to the project's existing theme system)
If the project already has a dark-mode toggle, wire CometChatThemeProvider's mode prop to the same source. RN doesn't have CSS selectors — the React Native theme is just a value held somewhere in JS, and you forward that value to CometChat. Three common shapes:
// Pattern A — OS-driven (no toggle yet, just react to system)
import { useColorScheme } from "react-native";
function ThemedRoot({ children }: { children: React.ReactNode }) {
const scheme = useColorScheme(); // "light" | "dark" | null
return (
<CometChatThemeProvider theme={{ mode: scheme === "dark" ? "dark" : "light" }}>
{children}
</CometChatThemeProvider>
);
}// Pattern B — App-controlled toggle via custom Context
const ThemeContext = createContext<{ mode: "light" | "dark"; toggle: () => void }>({
mode: "light",
toggle: () => {},
});
function ThemedRoot({ children }: { children: React.ReactNode }) {
const { mode } = useContext(ThemeContext);
return (
<CometChatThemeProvider theme={{ mode }}>{children}</CometChatThemeProvider>
);
}// Pattern C — react-native-paper (or any provider that exposes a theme)
import { useTheme } from "react-native-paper";
function ThemedRoot({ children }: { children: React.ReactNode }) {
const paperTheme = useTheme();
return (
<CometChatThemeProvider theme={{ mode: paperTheme.dark ? "dark" : "light" }}>
{children}
</CometChatThemeProvider>
);
}How to tell which pattern the project uses:
| Library / setup | Where the mode lives | Wire theme={{ mode: ... }} to |
|---|---|---|
| Plain RN, no toggle yet | useColorScheme() from react-native | scheme === "dark" ? "dark" : "light" |
| Custom React Context | useContext(ThemeContext).mode (or whatever shape it has) | Read from the context |
react-native-paper | useTheme().dark | dark ? "dark" : "light" |
restyle | useTheme<Theme>().colors (no built-in mode flag — track separately) | A sibling Context that holds the mode string |
tamagui | useThemeName() returns the current theme name | name === "dark" ? "dark" : "light" |
Appearance.addChangeListener (manual OS) | A useState that mirrors Appearance.getColorScheme() | Read from that state |
Rule: wherever the project's theme toggle writes its current state, read from THAT and forward to CometChatThemeProvider's mode prop. Don't keep two parallel sources of truth.
Don't combine Pattern A with Pattern B unless the user explicitly wants "follow OS until the user opens settings and overrides." That hybrid is legitimate but usually over-engineered for a first integration — ship Pattern B alone if the project has a toggle, Pattern A if it doesn't.
Placement in the wrapper chain
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.
---
3. Color tokens
Every color is a hex string ("#F76808" — never "rgb(...)" or named colors).
Primary (brand accent)
| 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. |
Neutrals (surfaces + borders)
| 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 |
Background aliases
| Token | Maps to (default) | Controls |
|---|---|---|
background1 | neutral50 | Main app background |
background2 | neutral100 | Sidebars, panels |
background3 | neutral200 | Nested panels, cards |
background4 | neutral300 | Additional surface |
Text
| Token | Default | Controls |
|---|---|---|
textPrimary | neutral900 | Main body text |
textSecondary | neutral600 | Timestamps, subtitles |
textTertiary | neutral500 | Hints, placeholders |
textHighlight | primary | Links, mentions |
Icon
| Token | Default | Controls |
|---|---|---|
iconPrimary | neutral900 | Active / default icons |
iconSecondary | neutral500 | Inactive icons |
iconHighlight | primary | Action icons |
Semantic (state indicators)
| Token | Default (light) | Controls |
|---|---|---|
info | #0B7BEA | Info callouts, links |
warning | #FFAB00 | Warning callouts |
success | #09C26F | Online indicator, success messages |
error | #F44649 | Error messages, validation |
Bubble-specific
| Token | Default | Controls |
|---|---|---|
sendBubbleBackground | primary | Outgoing bubble bg |
sendBubbleText | staticWhite (#FFFFFF) | Outgoing bubble text |
receiveBubbleBackground | neutral300 | Incoming bubble bg |
receiveBubbleText | neutral900 | Incoming bubble text |
Static (never flip light/dark)
| Token | Value | Controls |
|---|---|---|
staticBlack | #141414 | Fixed dark elements (overlays, opacity-based) |
staticWhite | #FFFFFF | Fixed light elements |
---
4. Mode: light / dark / system
Follow system
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>Force a specific mode
<CometChatThemeProvider theme={{ mode: "dark" }}>{/* ... */}</CometChatThemeProvider>Toggle controlled by your app
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.
Dark-mode palette
Override the dark branch of the theme for a custom dark palette:
<CometChatThemeProvider
theme={{
light: { color: { primary: "#6852D6" } },
dark: { color: { primary: "#A594F3", background1: "#0B0B0F" } },
}}
>---
5. Typography overrides
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.
Custom font setup
React Native font loading is NOT covered by the UI Kit — use your project's existing font system:
- Expo:
useFonts()fromexpo-font, load before rendering the provider - Bare RN: add fonts to
ios/<App>/Info.plistUIAppFonts+android/app/src/main/assets/fonts/+ runnpx react-native-asset
Only reference a fontFamily in the theme once the font is actually loaded — otherwise iOS shows the system default and Android crashes.
---
6. Per-component style blocks
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).
Source of truth for available keys
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).
---
7. Common recipes
Match a brand color (most common)
<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.
Dark mode + custom brand
<CometChatThemeProvider
theme={{
light: { color: { primary: "#FF6B35" } },
dark: { color: { primary: "#FF8F66", background1: "#1A1A1A" } },
}}
>
<App />
</CometChatThemeProvider>Custom message-bubble colors
<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).
Custom font across the whole UI
1. Load font (Expo useFonts or bare npx react-native-asset) 2. Override the typography block:
<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" },
},
},
}}
>---
8. Reading the theme in custom views
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.
---
9. Localization — CometChatI18nProvider
Theming 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.
9a. Built-in locales
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-tw9b. Wrapper chain with i18n — five wrappers, not four
CometChatI18nProvider 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>9c. Auto-detect (default)
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 RN9d. Force a specific language
Override 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.
9e. Fallback behavior
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.
9f. Custom translations — override or add languages
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.
9g. Reading the language inside custom views
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.
9h. Common pitfall — i18n outside the provider
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.
---
10. Anti-patterns
1. Don't pass non-hex colors. "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).
2. Don't override `staticBlack` / `staticWhite`. They're "static" for a reason — used in places where a specific absolute color is needed regardless of theme (overlays, badges on fixed-color avatars). Overriding them breaks visual consistency.
3. Don't override extended primary colors unless you need to. 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.
4. Don't wrap `CometChatThemeProvider` inside a screen. It belongs at the app root, once. Re-wrapping per screen creates hydration-like flashes on navigation and breaks dark-mode switching.
5. Don't mix theme overrides and per-component `style={}` for the same property. style={} wins — the theme override becomes dead code. Pick one: theme for app-wide, style={} for one-offs.
6. Don't reference an unloaded font in typography. iOS silently falls back to system default; Android crashes. Gate the provider on font loading:
// 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>
);7. Don't bypass the theme via `useColorScheme()` in a custom view. Call 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.
---
11. Verifying a theme change
After changing the theme:
1. Hard-reload the Metro bundler (not Fast Refresh — theme context sometimes doesn't update on Fast Refresh) 2. Send a message — check the outgoing bubble color matches primary / sendBubbleBackground 3. Toggle dark mode on the device (iOS: Settings → Display; Android: Settings → Display → Dark theme) 4. Check that both modes render without reloading the app
If something looks unstyled or crashes:
- Check the color is a hex string (not a name or rgb)
- Check the font (if you overrode typography) is actually loaded
- Check the override key matches the type in
packages/ChatUiKit/src/theme/type.ts
---
Skill routing reference
| 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 |