🎨 Theming
How we manage the UI and theming of the application.
We have standardized on Tailwind CSS implemented through the NativeWind library. This choice ensures a fast, consistent, and highly maintainable styling experience for AppCatalyst RN.
🛠️ The Styling Experience
If you are familiar with Tailwind CSS from web development, you will find the mobile styling process very intuitive. The utility-first approach means you can often transfer styling directly from a web application to your React Native components with only minor adjustments.
🚀 About NativeWind
NativeWind is the crucial library that enables you to use Tailwind CSS syntax within your React Native application.
How it Works
NativeWind achieves native performance by intelligently pre-compiling your Tailwind CSS classes into efficient React Native Stylesheets. This process minimizes runtime overhead while maximizing developer productivity.
NativeWind Version 4 Advantage
The kit leverages the improvements in NativeWind Version 4. This new approach eliminates the need to create and wrap components with a separate styled function. The utility-first styling process is simplified,
allowing you to apply styles directly within your JSX elements using simple class names.
For detailed usage, setup, and advanced customization, please refer to the official NativeWind documentation: 👉 NativeWind Overview
Here is an example of how your component should look like:
import { Link } from 'expo-router';
import React from 'react';
import { Pressable, Text, View } from '@/components/ui'; // NativeWind-wrapped UI components
import { Ionicons } from '@expo/vector-icons';
type SettingsRowProps = {
label: string;
iconName: keyof typeof Ionicons.glyphMap;
href: string;
isDestructive?: boolean;
};
export const SettingsRow = ({ label, iconName, href, isDestructive = false }: SettingsRowProps) => {
// Define the base color based on the 'isDestructive' prop (resolves to standard Tailwind colors)
const iconColor = isDestructive ? 'rgb(239, 68, 68)' : 'rgb(59, 130, 246)';
return (
<Link href={href} asChild>
<Pressable>
{/* Main container styles:
flex-row: flexDirection: 'row'
items-center: alignItems: 'center'
justify-between: justifyContent: 'space-between'
border-b: borderBottomWidth: 1
border-gray-100: borderBottomColor: #f3f4f6
px-4: paddingHorizontal: 16
py-3: paddingVertical: 12
dark:border-neutral-700: borderBottomColor: #404040 (in dark mode)
*/}
<View className="flex-row items-center justify-between border-b border-gray-100 px-4 py-3 dark:border-neutral-700">
{/* Left side container styles:
flex-row: flexDirection: 'row'
space-x-4: gap: 16 (or margin equivalent)
*/}
<View className="flex-row items-center space-x-4">
{/* Icon container styles:
h-8 w-8: height: 32, width: 32
rounded-lg: borderRadius: 8
bg-gray-100: backgroundColor: #f3f4f6
dark:bg-neutral-800: backgroundColor: #262626 (in dark mode)
*/}
<View className="h-8 w-8 items-center justify-center rounded-lg bg-gray-100 dark:bg-neutral-800">
<Ionicons name={iconName} size={20} color={iconColor} />
</View>
{/* Label text styles:
text-base: fontSize: 16
font-medium: fontWeight: 500
text-neutral-800: color: #262626 (or resolved color)
dark:text-neutral-200: color: #e5e5e5 (in dark mode)
*/}
<Text className={`text-base font-medium ${isDestructive ? 'text-red-500' : 'text-neutral-800 dark:text-neutral-200'}`}>
{label}
</Text>
</View>
{/* Icon color uses Tailwind gray-400 or red-500 values */}
<Ionicons
name="chevron-forward"
size={20}
color={isDestructive ? 'rgb(239, 68, 68)' : 'rgb(156, 163, 175)'}
/>
</View>
</Pressable>
</Link>
);
};Configuration
Nativewind is the same as Tailwind CSS, it comes with a default theme and colors that you can override by creating your own theme and colors.
You need to understand that Nativewind is a library that is built on top of Tailwind CSS. Feel free to add any Tailwind CSS config that you want to use in your application such as updating colors, spacing, typography, etc.
We have created a styles/global.css file where you can find our custom colors t
hat have been imported into tailwind.config.js and used as a theme for our demo application.
You can add your own color palette and use them in your components with Tailwind class names.
You can read more about how to configure your project with Tailwind CSS .
Dark Mode
Why dark mode?
Dark mode has gained significant traction in recent years and has become an expected feature to have. By applying dark mode, it makes it easier on the eyes in low-light environments and reduces eye strain, which means more time spent on your app.
This template comes with dark mode support out of the box, and it’s very easy to customize the color scheme of your app. Thanks to tailwindcss
Implementation
Since we’re using nativewind (which uses Tailwind CSS under the hood) and expo-router,
we let them handle the application of theme, and we just take care of the colors we want.
We define our colors in styles/global.css using CSS variables (HSL format), then convert them to HSL strings in constants/Colors.ts.
We use these colors in our hook useThemeConfig.tsx to get the theme object that we pass to ThemeProvider for React Navigation.
NativeWind automatically handles dark mode for our components using the dark: prefix based on the color scheme.
This is using for both React Navigation and NativeWind to have a consistent theme across the app.
Change the colors in constants/Colors.ts to update the theme of the app.
export const Colors = {
light: {
background: "hsl(0 0% 100%)",
foreground: "hsl(20 14.3% 4.1%)",
card: "hsl(0 0% 100%)",
primary: "hsl(47.29 100% 50%)",
destructive: "hsl(0 84.2% 60.2%)",
border: "hsl(20 5.9% 90%)",
},
dark: {
background: "hsl(20 14.3% 4.1%)",
foreground: "hsl(60 9.1% 97.8%)",
card: "hsl(20 14.3% 4.1%)",
primary: "hsl(43 97% 43%)",
destructive: "hsl(0 72.2% 50.6%)",
border: "hsl(12 6.5% 15.1%)",
},
};
// Navigation theme for React Navigation
export const NAV_THEME = {
light: {
background: Colors.light.background,
border: Colors.light.border,
card: Colors.light.card,
notification: Colors.light.destructive,
primary: Colors.light.primary,
text: Colors.light.foreground,
},
dark: {
background: Colors.dark.background,
border: Colors.dark.border,
card: Colors.dark.card,
notification: Colors.dark.destructive,
primary: Colors.dark.primary,
text: Colors.dark.foreground,
},
};
This is where we define our CSS variables for both light and dark themes. All colors are used in project getting from these variables. Otherwise getting from defualt colors in Colors.ts
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
...
}
.dark:root {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
...
}Add dark mode for each component
With NativeWind and darkMode: 'class' configured in tailwind.config.js, components automatically adapt to the current color
scheme without needing the dark: prefix. Here’s an example:
<CardHeader>
<CardTitle>
<View className='flex flex-row items-center gap-2'>
<Icon name='Bell' size={20} className='text-foreground' />
<Text variant='large'>Push Notifications</Text>
</View>
</CardTitle>
</CardHeader>How It Works:
Semantic Color Classes: Using semantic color classes like text-foreground, bg-background, bg-card, border-border, etc.,
automatically switches between light and dark values based on the active color scheme.
<CardHeader>
<CardTitle>
<View className='flex flex-row items-center gap-2'>
// text-foreground automatically adapts to light/dark mode
<Icon name='Bell' size={20} className='text-foreground' />
<Text variant='large'>Push Notifications</Text>
</View>
</CardTitle>
</CardHeader>