Theming System
Relevant source files
The following files were used as context for generating this wiki page:The application employs a robust and centralized theming system to manage UI appearance, primarily supporting dynamic switching between light and dark modes. The system is built around a single source of truth for colors, integrates seamlessly with NativeWind (Tailwind CSS for React Native), and uses React Context to propagate theme state throughout the component tree. User theme preferences are persisted across sessions using AsyncStorage.
The root of the application is wrapped in the ThemeProvider, which orchestrates theme state management, persistence, and application of styles across both native and web platforms. This ensures a consistent look and feel everywhere.
Sources: [app/_layout.tsx:13](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/app/_layout.tsx#L13), [lib/theme-provider.tsx](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/lib/theme-provider.tsx)
Architecture Overview
The theming architecture is designed to be modular and maintainable. It starts with a base configuration file that defines all color tokens. These tokens are then processed and integrated into different parts of the application, such as the Tailwind CSS configuration and a runtime theme provider.
The following diagram illustrates the data flow from the core configuration to the final rendered components.
This diagram shows the flow of theme data from the central theme.config.js file, through processing and integration layers, to the components that consume the theme values.
Sources: [theme.config.js](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/theme.config.js), [lib/_core/theme.ts](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/lib/_core/theme.ts), [tailwind.config.js](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/tailwind.config.js), [lib/theme-provider.tsx](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/lib/theme-provider.tsx)
Core Configuration
Color Token Definition
The single source of truth for all theme colors is theme.config.js. This file exports a themeColors object, which defines light and dark mode hex values for each semantic color token.
// theme.config.js
/** @type {const} */
const themeColors = {
primary: { light: '#0a7ea4', dark: '#0a7ea4' },
background: { light: '#ffffff', dark: '#151718' },
surface: { light: '#f5f5f5', dark: '#1e2022' },
foreground: { light: '#11181C', dark: '#ECEDEE' },
muted: { light: '#687076', dark: '##9BA1A6' },
border: { light: '#E5E7EB', dark: '#334155' },
success: { light: '#22C55E', dark: '#4ADE80' },
warning: { light: '#F59E0B', dark: '#FBBF24' },
error: { light: '#EF4444', dark: '#F87171' },
};
module.exports = { themeColors };
Sources: [theme.config.js:2-13](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/theme.config.js#L2-L13)
The available color tokens are:
| Token | Description |
|---|---|
primary | Accent color for interactive elements like buttons and links. |
background | The main background color for screens. |
surface | Background color for elevated elements like cards and modals. |
foreground | Primary color for text. |
muted | Secondary color for less important text, hints, and icons. |
border | Color for dividers and element borders. |
success | Color indicating a successful operation. |
warning | Color indicating a potential issue or warning. |
error | Color indicating an error or failure. |
Sources: [design.md:7-19](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/design.md#L7-L19), [theme.config.js](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/theme.config.js)
Palette Generation
The lib/_core/theme.ts file consumes the raw themeColors and processes them into structured palettes for both application logic and styling.
buildSchemePalette: This function transforms thethemeColorsobject into two separate palettes,lightanddark, creating theSchemeColorsobject.buildRuntimePalette: This function takes a scheme ('light' or 'dark') and builds a more comprehensive palette that includes semantic aliases (text,tint,icon, etc.) for common use cases, exported asColors.
// lib/_core/theme.ts
function buildSchemePalette(colors: ThemeColorTokens): SchemePalette {
// ... implementation ...
}
export const SchemeColors = buildSchemePalette(ThemeColors);
function buildRuntimePalette(scheme: ColorScheme): RuntimePalette {
const base = SchemeColors[scheme];
return {
...base,
text: base.foreground,
background: base.background,
tint: base.primary,
icon: base.muted,
tabIconDefault: base.muted,
tabIconSelected: base.primary,
border: base.border,
};
}
export const Colors = {
light: buildRuntimePalette("light"),
dark: buildRuntimePalette("dark"),
};
Sources: [lib/_core/theme.ts:14-48](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/lib/_core/theme.ts#L14-L48)
Integration with NativeWind & Tailwind CSS
The theming system is tightly integrated with NativeWind to enable styling via Tailwind CSS utility classes.
The tailwind.config.js file dynamically generates theme-aware color utilities. It reads the themeColors and configures Tailwind to use CSS variables for each color. This allows the colors to be changed at runtime by updating the CSS variables on the root element.
// tailwind.config.js
const { themeColors } = require("./theme.config");
const tailwindColors = Object.fromEntries(
Object.entries(themeColors).map(([name, swatch]) => [
name,
{
DEFAULT: `var(--color-${name})`,
light: swatch.light,
dark: swatch.dark,
},
]),
);
module.exports = {
darkMode: "class",
// ...
theme: {
extend: {
colors: tailwindColors,
},
},
// ...
};
The darkMode: "class" strategy relies on the presence of a .dark class on the <html> element for web, which is managed by the ThemeProvider.
Sources: [tailwind.config.js:1-21](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/tailwind.config.js#L1-L21)
Theme Provider and Context
The ThemeProvider component is the central controller for the app's theme. It is placed at the root of the application in app/_layout.tsx.
Sources: [app/_layout.tsx:100](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/app/_layout.tsx#L100)
Key Responsibilities
- State Management: Holds the current
colorScheme('light' or 'dark') in its state. - Persistence: On initial load, it reads the saved theme preference from
AsyncStorage(key:appColorScheme). - System Preference: If no saved preference is found, it defaults to the user's system-wide color scheme setting.
- Theme Application: When the theme changes, it applies the new scheme to all relevant systems:
- NativeWind: Calls
nativewindColorScheme.set(scheme). - React Native: Calls
Appearance.setColorScheme(scheme). - Web: Sets the
data-themeattribute and toggles thedarkclass on the<html>element, and updates CSS custom properties.
- NativeWind: Calls
- Context Provision: It exposes the current
colorSchemeand thesetColorSchemefunction to the entire app viaThemeContext.
Theme Change Flow
The following sequence diagram shows what happens when a user toggles the theme.
This diagram illustrates the sequence of events when a user action triggers a theme change, from the component interaction to state update, platform-specific application, and finally persistence.
Sources: [lib/theme-provider.tsx:14-72](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/lib/theme-provider.tsx#L14-L72)
Consuming Theme Values
There are two primary ways for components to consume theme colors.
1. Using NativeWind Utility Classes
The most common method is to use the generated Tailwind CSS utility classes. These classes automatically respond to theme changes.
import { View, Text } from "react-native";
// This component will automatically adapt to light/dark mode.
export function MyThemedComponent() {
return (
<View className="bg-surface border border-border p-4">
<Text className="text-foreground">Hello, World!</Text>
<Text className="text-muted">This is a themed component.</Text>
</View>
);
}
Sources: [app/dev/theme-lab.tsx:90-93](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/app/dev/theme-lab.tsx#L90-L93), [tailwind.config.js:1-13](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/tailwind.config.js#L1-L13)
2. Using the useColors Hook
For imperative styling (e.g., with StyleSheet or for component props that require a color string), the useColors hook provides direct access to the current theme's color palette. The hook returns the appropriate RuntimePalette from Colors based on the active theme.
import { View, Text, StyleSheet } from "react-native";
import { useColors } from "@/hooks/use-colors";
import { IconSymbol } from "@/components/ui/icon-symbol";
export function MyStyledComponent() {
const colors = useColors(); // Gets the current theme's color palette
return (
<View style={{ backgroundColor: colors.surface }}>
<IconSymbol name="house.fill" color={colors.tint} size={20} />
<Text style={{ color: colors.text }}>Styled with useColors.</Text>
</View>
);
}
The useColors hook is demonstrated in the theme-lab.tsx screen, where it's used to display the current values and style components imperatively.
Sources: [app/dev/theme-lab.tsx:39](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/app/dev/theme-lab.tsx#L39), [app/dev/theme-lab.tsx:145-148](https://github.com/Pirzl/aidoit4u-mobile-app/blob/HEAD/app/dev/theme-lab.tsx#L145-L148)
Summary
The theming system is a well-structured, centralized, and flexible solution for managing the application's visual identity. By leveraging a single configuration file, integrating with NativeWind for declarative styling, and providing hooks for imperative use cases, it offers a comprehensive approach to theming. The automatic persistence of user preferences ensures a personalized experience across app sessions.