1

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.

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.