Theming
Wrap your app once, read theme values with useTheme, and customize colors without guessing which object to use.
Theme Basics
A theme is the shared style system for your app. It keeps colors, spacing, typography, borders, and layout helpers in one place so every screen can use the same values.
The flow is simple: wrap the app with ThemeProvider, call useTheme inside a screen, then use the object that matches what you are styling.
- colors: plain color values. Use colors.primary when a prop needs a color string.
- backgrounds: ready-made backgroundColor styles. Use backgrounds.primary on a View.
- gutters: spacing helpers. Use gutters.padding_16, gutters.margin_12, or gutters.gap_8.
- layout: flexbox helpers. Use layout.row, layout.itemsCenter, layout.justifyCenter, or layout.flex_1.
- fonts: text color and font helpers. Use fonts.primary or fonts.size_16 in Text styles.
- borders: border color, radius, and width helpers. Use borders.gray8, borders.rounded_16, or borders.w_1.
- typographies: text presets. Use typographies.heading3, typographies.body1, or typographies.body2.
- navigationTheme: pass this to React Navigation when navigation should follow the active theme.
Root Setup
Wrap the app with ThemeProvider before rendering package components. Add UiPortalProvider inside it when the app uses toast, dialog, bottom sheet, or context menu managers.
import {
ThemeProvider,
UiPortalProvider,
} from '@sohantalukder/rn-kit';
import { AccountScreen } from './src/screens/AccountScreen';
export function App() {
return (
<ThemeProvider>
<UiPortalProvider>
<AccountScreen />
</UiPortalProvider>
</ThemeProvider>
);
}Use Theme Values
Call useTheme inside any component rendered below ThemeProvider. Compose the returned helpers in normal React Native style arrays.
This example uses layout for flex direction, gutters for spacing, backgrounds for the card background, borders for the outline, typographies for the heading size, and fonts for the heading color.
import { View } from 'react-native';
import { Text, useTheme } from '@sohantalukder/rn-kit';
export function ProfileSummary() {
const { backgrounds, borders, fonts, gutters, layout, typographies } =
useTheme();
return (
<View
style={[
layout.row,
layout.itemsCenter,
gutters.gap_12,
gutters.padding_16,
backgrounds.background,
borders.rounded_16,
borders.w_1,
borders.gray8,
]}
>
<Text style={[typographies.heading3, fonts.primary]}>
Account
</Text>
<Text color="secondary">Ready to review</Text>
</View>
);
}Theme Objects
These are the objects returned by useTheme. Choose the object by what the React Native prop expects.
- Need a raw color? Use colors.primary.
- Need a background style? Use backgrounds.primary.
- Need spacing? Use gutters.padding_16, gutters.margin_16, or gutters.gap_16.
- Need flexbox layout? Use layout.row, layout.flex_1, layout.itemsCenter, or layout.justifyCenter.
- Need text styling? Use fonts.primary with typographies.heading3.
- Need border styling? Use borders.gray8, borders.w_1, and borders.rounded_16.
- Need React Navigation styling? Use navigationTheme.
- Need to switch mode? Use changeTheme with default, dark, or system.
Theme Modes
rn-kit supports default, dark, and system. default is the light theme. dark forces the dark theme. system follows the device color scheme.
Use changeTheme when a user selects a theme from settings.
import { Button, useTheme } from '@sohantalukder/rn-kit';
export function ThemeActions() {
const { changeTheme } = useTheme();
return (
<>
<Button text="Use light" onPress={() => changeTheme('default')} />
<Button text="Use dark" onPress={() => changeTheme('dark')} />
<Button text="Follow system" onPress={() => changeTheme('system')} />
</>
);
}Save User Choice
ThemeProvider does not force one storage library. Give it a small storageAdapter that reads and writes default, dark, or system in your own app storage.
When no saved value exists, ThemeProvider stores system and follows the operating system color scheme.
<ThemeProvider
storageAdapter={{
getTheme: () => localStore.getTheme(),
setTheme: value => localStore.setTheme(value),
}}
>
<App />
</ThemeProvider>Customize App Colors
Most apps only need to pass brand colors to ThemeProvider. Start with colors. Any token added to colors is also generated as backgrounds.token, fonts.token, and borders.token, so the same token can be used across views, text, and borders.
Add dark overrides inside variants.dark when the dark value should be different. You do not need to repeat every color in dark mode, only the values that should change.
import { ThemeProvider } from '@sohantalukder/rn-kit';
import App from './src/App';
const appTheme = {
colors: {
primary: '#2563EB',
brand: '#2563EB',
accent: '#7C3AED',
},
variants: {
dark: {
colors: {
primary: '#60A5FA',
brand: '#60A5FA',
accent: '#C4B5FD',
},
},
},
};
export default function Main() {
return (
<ThemeProvider theme={appTheme}>
<App />
</ThemeProvider>
);
}Use Custom Colors
Use the same token from the object that matches the prop. In this example, colors.brand is a color string for an icon, backgrounds.brand is a View style, fonts.white is a Text style, and borders.brand is a border style.
import { View } from 'react-native';
import { IconByVariant, Text, useTheme } from '@sohantalukder/rn-kit';
export function BrandBanner() {
const { backgrounds, borders, colors, fonts, gutters } = useTheme();
return (
<View
style={[
backgrounds.brand,
borders.brand,
borders.w_1,
borders.rounded_16,
gutters.padding_16,
]}
>
<Text style={fonts.white}>Brand announcement</Text>
<IconByVariant path="check" color={colors.brand} />
</View>
);
}Advanced Color Overrides
Use colors when one token should create raw, background, font, and border helpers. Use backgrounds, fonts.colors, or borders.colors when a token should only exist in one style group.
Use navigationColors when React Navigation should match your app theme. Pass the returned navigationTheme from useTheme to your NavigationContainer.
const appTheme = {
colors: {
brand: '#2563EB',
},
backgrounds: {
brandSurface: '#EFF6FF',
},
fonts: {
colors: {
mutedText: '#64748B',
},
},
borders: {
colors: {
brandOutline: '#93C5FD',
},
},
navigationColors: {
primary: '#2563EB',
},
variants: {
dark: {
colors: {
brand: '#60A5FA',
},
backgrounds: {
brandSurface: '#172554',
},
fonts: {
colors: {
mutedText: '#CBD5E1',
},
},
borders: {
colors: {
brandOutline: '#2563EB',
},
},
navigationColors: {
primary: '#60A5FA',
},
},
},
};Change Package Defaults
If you are maintaining this package and want to change its built-in defaults, edit src/theme/_config.ts. This is different from app-level customization with ThemeProvider theme.
Add the token to colorsLight and colorsDark, then keep the maps connected through colors, backgrounds, fonts.colors, and borders.colors. Edit the sizes array when you need new gutter or font-size helper keys.
const colorsLight = {
// existing tokens...
brand: '#2563EB',
} as const;
const colorsDark = {
// existing tokens...
brand: '#60A5FA',
} as const;Common Questions
- My custom color is missing: make sure the component is rendered below the ThemeProvider that receives theme={appTheme}.
- I only need a one-off screen color: prefer adding a named token to the theme instead of hardcoding colors in many files.
- I need new spacing helpers: app-level theme overrides do not add gutter sizes; package maintainers should edit the sizes array in src/theme/_config.ts.
- My navigation colors do not change: pass navigationTheme from useTheme to your React Navigation container.
Guidelines
- Start with the default theme before adding custom tokens.
- Use clear token names like brand, accent, surfaceWarning, or mutedText.
- Add dark overrides only for values that need different contrast in dark mode.
- Prefer theme tokens over one-off colors in app screens.
- Use the docs theme toggle or Storybook toolbar to preview default and dark variants.
- Use navigationTheme from useTheme when React Navigation should match the active variant.