Carlos Saraiva
Carlos Saraiva

Reputation: 21

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.

Upvotes: 1

Views: 1904

Answers (1)

Carlos Saraiva
Carlos Saraiva

Reputation: 21

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.

Upvotes: 1

Related Questions