Search code examples
reactjsreact-nativeexpotailwind-cssnativewind

Nativewind And Tailwind Multiple Theme


I'm trying to create multiple themes for a monorepo project (managed with yarn workspaces) with expo for Mobile (managed workflow) and reactjs for Web (using Vite and both with a typescript template) with a shared folder, which would have the themes configuration, but I can not find any good information about how to do it, any solution or source is appreciated. Here is how my tailwind.config.js files are set in each platform/project.

For Mobile:

/** @type {import('tailwindcss').Config} */
module.exports = {
  presets: [require('@expo-monorepo/shared/tailwind.config')],
  content: [
    './index.{js,jsx,ts,tsx}',
    './App.{js,jsx,ts,tsx}',
    './src/**/*.{js,jsx,ts,tsx}',
    '../Shared/**/*.{js,jsx,ts,tsx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [require('nativewind/tailwind/native')],
};

Shared Folder:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [],
  theme: {
    extend: {
      colors: {
        'pastel-green': {
          50: '#f1fdf0',
          100: '#dbfddb',
          200: '#bbf8ba',
          300: '#85f184',
          400: '#63e663',
          500: '#1ec91f',
          600: '#13a613',
          700: '#138214',
          800: '#146716',
          900: '#135415',
          950: '#042f06',
        },
        candlelight: {
          50: '#feffe7',
          100: '#fcffc1',
          200: '#fdff86',
          300: '#fffa41',
          400: '#ffee0d',
          500: '#ffdf00',
          600: '#d1a500',
          700: '#a67602',
          800: '#895c0a',
          900: '#744b0f',
          950: '#442804',
        },
      },
    },
  },
  plugins: [],
};

For Web:

/** @type {import('tailwindcss').Config} */
export default {
  presets: [require('@expo-monorepo/shared/tailwind.config')],
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};

I've tried to create a theme inside the shared folder using nativewind's NativeWindStyleSheet API. Already tried tailwindcss-themer plugin and tailwindcss/plugin. Maybe I just don't know how to configure them for mobile, but I was expecting to have the themes that I want, for example christmas, halloween, my-theme, etc, when I use colorScheme from useColorScheme.


Solution

  • I've made multiple themes in another project just by searching on the internet. I've found that there will be a new version of NativeWind which supports multiple themes (NativeWind v4). I've done it with an expo project using expo-router.

    Here is all the documentation that you need to do it in the following order:

    1. https://docs.expo.dev/tutorial/create-your-first-app/
    2. https://docs.expo.dev/routing/installation/#manual-installation
    3. https://www.nativewind.dev/v4/getting-started/expo-router

    After all the configuration following the documentation, I added some custom color variables inside the tailwind.config.js:

    /** @type {import('tailwindcss').Config} */
    module.exports = {
      content: ['./app/**/*.{js,jsx,ts,tsx}', './Themes/**/*.{js,jsx,ts,tsx}'],
      theme: {
        extend: {
          colors: {
            primary: 'var(--color-primary)',
            secondary: 'var(--color-secondary)',
            outstand: 'var(--color-outstand)',
          },
        },
      },
      plugins: [],
    };
    

    After that, I created a Themes folder with the following files:

    index.tsx

    import { useState, useCallback, createContext, useContext } from 'react';
    import { View, ViewProps } from 'react-native';
    import { StatusBarTheme, Themes, ThemesVariant } from './theme-config';
    import clsx from 'clsx';
    import { StatusBar } from 'expo-status-bar';
    
    type ThemeContextValues = {
      theme: ThemesVariant;
    };
    
    const ThemeProviderValues = createContext<ThemeContextValues>({
      theme: 'light',
    });
    
    export function useThemeContextValues() {
      return useContext(ThemeProviderValues);
    }
    
    type ThemeContextActions = {
      handleThemeSwitch: (newTheme: ThemesVariant) => void;
    };
    
    const ThemeProviderActions = createContext<ThemeContextActions>(
      {} as ThemeContextActions
    );
    
    export function useThemeContextActions() {
      return useContext(ThemeProviderActions);
    }
    
    type ThemeProps = ViewProps;
    
    export function Theme(props: ThemeProps) {
      const [theme, setTheme] = useState<ThemesVariant>('light');
    
      const handleThemeSwitch = useCallback((newTheme: ThemesVariant) => {
        setTheme(newTheme);
      }, []);
    
      return (
        <View style={Themes[theme]} className={clsx('flex-1', props.className)}>
          <ThemeProviderValues.Provider value={{ theme }}>
            <ThemeProviderActions.Provider value={{ handleThemeSwitch }}>
              <StatusBar
                style={StatusBarTheme[theme].style}
                backgroundColor={StatusBarTheme[theme].background}
              />
              {props.children}
            </ThemeProviderActions.Provider>
          </ThemeProviderValues.Provider>
        </View>
      );
    }
    

    theme-config.ts

    import { StatusBarStyle } from 'expo-status-bar';
    import { vars } from 'nativewind';
    
    export type ThemesVariant = 'light' | 'xmas' | 'dark' | 'halloween';
    
    export const Themes = {
      light: vars({
        '--color-primary': '#000000',
        '--color-secondary': '#ffffffff',
        '--color-outstand': '#2288dd',
      }),
      dark: vars({
        '--color-primary': '#ffffff',
        '--color-secondary': '#000000',
        '--color-outstand': '#552288',
      }),
      xmas: vars({
        '--color-primary': '#fff',
        '--color-secondary': '#3225de',
        '--color-outstand': '#0ca90c',
      }),
      halloween: vars({
        '--color-primary': '#000000',
        '--color-secondary': '#5522dd',
        '--color-outstand': '#ffcc00',
      }),
    };
    
    type StatusBarThemeStyle = {
      [keys in ThemesVariant]: {
        style: StatusBarStyle;
        background: string;
      };
    };
    
    export const StatusBarTheme: StatusBarThemeStyle = {
      light: {
        style: 'dark',
        background: '#fff',
      },
      dark: {
        style: 'light',
        background: '#000',
      },
      xmas: {
        style: 'light',
        background: '#3225de',
      },
      halloween: {
        style: 'dark',
        background: '#52d',
      },
    };
    

    and ThemeSwitcher.tsx

    import { Pressable, Text, View } from 'react-native';
    import { useThemeContextActions } from '.';
    
    export function ThemeSwitcher() {
      const { handleThemeSwitch } = useThemeContextActions();
      return (
        <View className="p-5 flex-row flex-wrap gap-y-5 w-full justify-evenly">
          <Pressable
            onPress={() => handleThemeSwitch('light')}
            className="p-2 rounded-lg items-center bg-outstand justify-center w-40 h-36 shadow-lg shadow-black"
          >
            <Text className="text-lg font-semibold text-primary">Light</Text>
          </Pressable>
    
          <Pressable
            onPress={() => handleThemeSwitch('dark')}
            className="p-2 rounded-lg items-center bg-outstand justify-center w-40 h-36 shadow-lg shadow-black"
          >
            <Text className="text-lg font-semibold text-primary">Dark</Text>
          </Pressable>
    
          <Pressable
            onPress={() => handleThemeSwitch('xmas')}
            className="p-2 rounded-lg items-center bg-outstand justify-center w-40 h-36 shadow-lg shadow-black"
          >
            <Text className="text-lg font-semibold text-primary">Christmas</Text>
          </Pressable>
    
          <Pressable
            onPress={() => handleThemeSwitch('halloween')}
            className="p-2 rounded-lg items-center bg-outstand justify-center w-40 h-36 shadow-lg shadow-black"
          >
            <Text className="text-lg font-semibold text-primary">Halloween</Text>
          </Pressable>
        </View>
      );
    }
    

    My app/index.tsx file, looks like this:

    import { Text, View } from 'react-native';
    import '../global.css';
    import { Theme } from '../Themes';
    import { ThemeSwitcher } from '../Themes/ThemeSwitcher';
    export default function App() {
      return (
        <Theme>
          <View className="flex-1 items-center justify-center bg-secondary">
            <Text className="text-primary text-lg font-semibold">
              Open up App.tsx to start working on your app!
            </Text>
    
            <ThemeSwitcher />
          </View>
        </Theme>
      );
    }
    

    If you want to see all the files, you can check in my repository where I have this project right here.