Initial Setup
Quickly create a new project using the React Native Reusables CLI.
Usage
Run the following command in your terminal, answer the prompts, and you’re good to go!
npx @react-native-reusables/cli@latest init
Starter-base template
File structure
Directoryapps
- _layout.tsx
- +not-found.tsx
- index.tsx
Directoryassets
Directoryimages
- adaptive-icon.png
- favicon.png
- icon.png
- splash.png
Directorycomponents
Directoryui
- avatar.tsx
- button.tsx
- card.tsx
- progress.tsx
- text.tsx
- tooltip.tsx
- ToggleTheme.tsx
Directorylib
Directoryicons
- iconWithClassName.ts
- Info.tsx
- MoonStar.tsx
- Sun.tsx
- android-navigation-bar.tsx
- constants.ts
- useColorScheme.tsx
- utils.ts
- .gitignore
- app.json
- babel.config.js
- global.css
- index.js
- metro.config.js
- nativewind-env.d.ts
- package.json
- README.md
- tailwind.config.js
- tsconfig.json
- Follow the installation guide for NativeWind from the official documentation
- Install the following packages:
Platforms: Web, iOS, and Android
npx expo install tailwindcss-animate class-variance-authority clsx tailwind-merge
Platforms: iOS and Android
npx expo install class-variance-authority clsx tailwind-merge
- Configure path aliases
We use the ~
alias. This is how you can configure it in your tsconfig.json
file:
{ "compilerOptions": { "baseUrl": ".", "paths": { "~/*": ["*"] } }}
- Add a cn helper
Add the following code to the ~/lib/utils.ts
file:
import { clsx, type ClassValue } from 'clsx';import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs));}
- Add the useColorScheme hook
Add the following code to the ~/lib/useColorScheme.tsx
file:
import { useColorScheme as useNativewindColorScheme } from 'nativewind';
export function useColorScheme() { const { colorScheme, setColorScheme, toggleColorScheme } = useNativewindColorScheme(); return { colorScheme: colorScheme ?? 'dark', isDarkColorScheme: colorScheme === 'dark', setColorScheme, toggleColorScheme, };}
- Add the following css variables to
~/global.css
file.
@tailwind base; @tailwind components; @tailwind utilities;
@layer base { :root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; --card: 0 0% 100%; --card-foreground: 240 10% 3.9%; --popover: 0 0% 100%; --popover-foreground: 240 10% 3.9%; --primary: 240 5.9% 10%; --primary-foreground: 0 0% 98%; --secondary: 240 4.8% 95.9%; --secondary-foreground: 240 5.9% 10%; --muted: 240 4.8% 95.9%; --muted-foreground: 240 3.8% 46.1%; --accent: 240 4.8% 95.9%; --accent-foreground: 240 5.9% 10%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; --border: 240 5.9% 90%; --input: 240 5.9% 90%; --ring: 240 5.9% 10%; }
.dark:root { --background: 240 10% 3.9%; --foreground: 0 0% 98%; --card: 240 10% 3.9%; --card-foreground: 0 0% 98%; --popover: 240 10% 3.9%; --popover-foreground: 0 0% 98%; --primary: 0 0% 98%; --primary-foreground: 240 5.9% 10%; --secondary: 240 3.7% 15.9%; --secondary-foreground: 0 0% 98%; --muted: 240 3.7% 15.9%; --muted-foreground: 240 5% 64.9%; --accent: 240 3.7% 15.9%; --accent-foreground: 0 0% 98%; --destructive: 0 72% 51%; --destructive-foreground: 0 0% 98%; --border: 240 3.7% 15.9%; --input: 240 3.7% 15.9%; --ring: 240 4.9% 83.9%; } }
- Add the following code in the
~/lib/constants.ts
file for the navigation theme colors:
export const NAV_THEME = { light: { background: 'hsl(0 0% 100%)', // background border: 'hsl(240 5.9% 90%)', // border card: 'hsl(0 0% 100%)', // card notification: 'hsl(0 84.2% 60.2%)', // destructive primary: 'hsl(240 5.9% 10%)', // primary text: 'hsl(240 10% 3.9%)', // foreground }, dark: { background: 'hsl(240 10% 3.9%)', // background border: 'hsl(240 3.7% 15.9%)', // border card: 'hsl(240 10% 3.9%)', // card notification: 'hsl(0 72% 51%)', // destructive primary: 'hsl(0 0% 98%)', // primary text: 'hsl(0 0% 98%)', // foreground }, };
If you changed the colors in the ~/global.css
file, update the ~/lib/constants.ts
file with the new colors.
Each color has a commented css variable name next to it.
- Use the CSS variables in your tailwind.config.js file.
Platforms: Web, iOS, and Android
const { hairlineWidth } = require('nativewind/theme');
/** @type {import('tailwindcss').Config} */module.exports = { darkMode: 'class', content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'], presets: [require('nativewind/preset')], theme: { extend: { colors: { border: 'hsl(var(--border))', input: 'hsl(var(--input))', ring: 'hsl(var(--ring))', background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))', primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))', }, secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))', }, destructive: { DEFAULT: 'hsl(var(--destructive))', foreground: 'hsl(var(--destructive-foreground))', }, muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))', }, accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))', }, popover: { DEFAULT: 'hsl(var(--popover))', foreground: 'hsl(var(--popover-foreground))', }, card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))', }, }, borderWidth: { hairline: hairlineWidth(), }, keyframes: { 'accordion-down': { from: { height: '0' }, to: { height: 'var(--radix-accordion-content-height)' }, }, 'accordion-up': { from: { height: 'var(--radix-accordion-content-height)' }, to: { height: '0' }, }, }, animation: { 'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out', }, }, }, plugins: [require('tailwindcss-animate')],};
Platforms: iOS and Android
const { hairlineWidth } = require('nativewind/theme');
/** @type {import('tailwindcss').Config} */module.exports = { darkMode: 'class', content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'], presets: [require('nativewind/preset')], theme: { extend: { colors: { border: 'hsl(var(--border))', input: 'hsl(var(--input))', ring: 'hsl(var(--ring))', background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))', primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))', }, secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))', }, destructive: { DEFAULT: 'hsl(var(--destructive))', foreground: 'hsl(var(--destructive-foreground))', }, muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))', }, accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))', }, popover: { DEFAULT: 'hsl(var(--popover))', foreground: 'hsl(var(--popover-foreground))', }, card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))', }, }, borderWidth: { hairline: hairlineWidth(), }, }, }, plugins: [],};
- Configure React Navigation Theme
In your root component (ex: the root _layout.tsx
if you’re using expo-router), add the following code
to load the selected theme, prevent the flash of the default theme,
and store the selected theme in the async storage.
import '~/global.css';
import { Theme, ThemeProvider, DefaultTheme, DarkTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import * as React from 'react'; import { Platform } from 'react-native'; import { NAV_THEME } from '~/lib/constants'; import { useColorScheme } from '~/lib/useColorScheme';
const LIGHT_THEME: Theme = { ...DefaultTheme, colors: NAV_THEME.light, }; const DARK_THEME: Theme = { ...DarkTheme, colors: NAV_THEME.dark, };
export { // Catch any errors thrown by the Layout component. ErrorBoundary, } from 'expo-router';
export default function RootLayout() { const hasMounted = React.useRef(false); const { colorScheme, isDarkColorScheme } = useColorScheme(); const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false);
useIsomorphicLayoutEffect(() => { if (hasMounted.current) { return; }
if (Platform.OS === 'web') { // Adds the background color to the html element to prevent white background on overscroll. document.documentElement.classList.add('bg-background'); } setIsColorSchemeLoaded(true); hasMounted.current = true; }, []);
if (!isColorSchemeLoaded) { return null; }
return ( <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}> <StatusBar style={isDarkColorScheme ? 'light' : 'dark'} /> <Stack /> </ThemeProvider> ); }
const useIsomorphicLayoutEffect = Platform.OS === 'web' && typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
- Add the
<ToggleTheme/>
icons
Follow the next step and add the <Sun/>
and <MoonStar/>
icons from the examples.