Easily set up multi-theming in Sitecore JSS using Next.js and Tailwind CSS (Part 1)

Easily set up multi-theming in Sitecore JSS using Next.js and Tailwind CSS (Part 1)

·

8 min read

Multi-theming refers to the ability to switch between different visual themes within an application. Themes can include changes to colors, typography, spacing, and other styling elements.

This article will provide step-by-step guidance on configuring multi-theming within a Sitecore JSS Headless/XM Cloud-based solution. The tutorial will specifically focus on utilizing NextJS and TailwindCSS to achieve this setup.

While installing Sitecore NextJS SDK from The JSS app initializer make sure to enable multisite addon when prompted.😄
This article is intended for individuals who are actively engaged in working on Sitecore JSS Headless. Feel absolutely liberated to give it a read if it tickles your curiosity! 😄

The process is structured into two distinct phases, each dedicated to the meticulous task of establishing a multi-theme configuration and effectively harnessing its capabilities within the NextJS application. In this article, we'll look into configuring the multi-theming.

Configure multi-theming

Let's explore the process of configuring multi-theming and understand the steps involved in customizing and managing multiple themes.

Theme mappings

To begin, include the following variable in your environment variables. When referring to theme mapping, it essentially signifies the correlation between a specific site and its associated theme.

NEXT_PUBLIC_THEME_MAPPINGS = {
  "primary":["site1"],
  "secondary":["site2", "site3"],
}

The NEXT_PUBLIC_THEME_MAPPINGS variable contains the theme mapping configurations. It's structured as an object, where the key represent the theme name, and the corresponding values are arrays of associated sites. It's important to note that the site names in the array should match those defined in Sitecore.

It's essential to note that a single theme can be utilized by multiple sites. This is why we pass an array of sites, allowing for flexibility in associating the same theme with different sites.
Make sure to prefix theme mapping variable with NEXT_PUBLIC as these variables are needed at client-side. Make sure to prefix theme mapping variable with NEXT_PUBLIC as these variables are needed at client-side. If the same site is configured for multiple themes, it is noteworthy that the initial detected theme will be applied.

Theme context

Create theme context

We're using React context to make our themes work smoothly. First, we need to set up the context and organize the theme details in ThemeContext. Check the image below to see what ThemeContext is all about.

//ThemeContext.tsx

import { createContext, useContext } from 'react';

export const ALL_THEMES = Object.keys(JSON.parse(process.env.NEXT_PUBLIC_THEME_MAPPINGS || '{}'));

type Themes = typeof ALL_THEMES;

export type ThemeName = Themes[number];

export type ThemeFile = {
  [key in ThemeName]: unknown;
};

export const ThemeContext = createContext<ThemeName>(ALL_THEMES[0]);

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useTheme = (themeFile?: ThemeFile) => {
  const themeName = useContext(ThemeContext);
  const themeData = themeFile ? themeFile[themeName] : undefined;
  return { themeName, themeData };
};

We can get mainly two variables from the context.

  1. themeName - This tells us the name of the current theme.

  2. themeData - This provides the details of the theme linked to the current theme.

We'll explore the above variables further in the later sections.

Use theme context

Now that our theme context is set up, we just have to use it in the app. For this article, which concentrates on Sitecore's JSS NextJS solution, remember to wrap the entire app with the theme context. You can refer to it at src/pages/_app.tsx

//_app.tsx

import { useState } from 'react';
import config from 'temp/config';
import { ThemeContext } from 'lib/context/ThemeContext';

function App({ Component, pageProps }: AppProps<SitecorePageProps>): JSX.Element {
  const { ...rest } = pageProps;
  const sites = JSON.parse(config.sites);

  const currentSite = sites.find(
    (_) => _.name === pageProps.layoutData?.sitecore?.context?.site?.name
  );

  const [site] = useState(currentSite || sites[0]);

  return (
    <ThemeContext.Provider value={getTheme(site.name)}>
      <Component {...rest} />
    </ThemeContext.Provider>
  );
}

export default App;
The config will be generated automatically in /temp folder whenever the "bootstrap" command is invoked in the package.json file. The reference to config.sites corresponds to the SITES variable, and it's essential to ensure that this variable is defined as an environment variable.
Don't worry about the getTheme function for now, We'll discuss it in the next section.

Detect current theme

Having successfully set up the theme context, the next step involves detecting the current theme and assigning its value to the theme context.

In this part, we bring in the getTheme function. Its job is to figure out and give us the current theme based on the current site. Create a new file in src/lib/multisite/theme-mapping.ts.

This function takes the current site name as an argument. Using the NEXT_PUBLIC_THEME_MAPPINGS environment variable, it figures out and gives us the current theme. Take a look at the code snippet below for a quick understanding.

//theme-mapping.ts

import { ThemeName } from 'lib/context/ThemeContext';

export const getTheme = (siteName: string): ThemeName => {
  // This object should be configured manually for theming
  const themesAssociatedToSites: Record<ThemeName, Array<string>> = JSON.parse(
    process.env.NEXT_PUBLIC_THEME_MAPPINGS || '{}'
  );

  const _themes = Object.keys(themesAssociatedToSites) as unknown as ThemeName;
  let currentTheme = Object.keys(themesAssociatedToSites)[0] as ThemeName;

  for (let i = 0; i < _themes.length; i++) {
    if (themesAssociatedToSites[_themes[i] as ThemeName].includes(siteName))
      return (currentTheme = _themes[i] as ThemeName);
  }

  return currentTheme;
};

Refer /pages/_app.tsx to understand how the above function is being used to detect the current theme.

Configure themes

It's time to set up Tailwind configurations for multiple themes. In this example, we'll configure our app with primary and secondary themes. To do this, install the tailwindcss-themer plugin for multi-theming.

The scope of this article is how to configure multi-theme using tailwind. We won’t be looking into how tailwind configuration works. You can find detailed instructions about tailwind configuration at Configuration - Tailwind CSS.

There are a couple of basic steps to follow beforehand.

  1. Setup base configuration file

  2. Setup theme configuration files

  3. Export themes

  4. Configure tailwind

Now, create a folder named theme in the src directory. We will place theme configuration files in this folder, which includes the following files.

  • index.js

  • tailwind.base.js (base-configuration-file)

  • tailwind.primary.js (theme-configuration-file)

  • tailwind.secondary.js (theme-configuration-file)

Setup base configuration file

This configuration file contains base theme configurations that are common for all themes, such as spacing, breakpoints, etc.

//sample of tailwind.base.js

module.exports = {
  extend: {
    name: 'base',
    screens: {
      lg: '1200px',
      ml: '960px',
      md: '720px',
      sm: '460px',
      xs: '320px',
    },
    spacing: {
      0: '0',
      1: '4px',
      2: '8px',
      3: '16px',
      4: '24px',
      5: '32px',
      6: '64px',
      7: '128px',
      8: '256px',
    },
  }
}

Setup theme configuration files

This configuration files contains theme specific configurations such as typography, colors, etc.

If you want to have a look at the files you can find theme in src/theme folder.

//tailwind.primary.js

module.exports = {
  name: 'primary',
  extend: {
    fontSize: {
      // Desktop font sizes
      xxl: ['7.5rem', '100%'], //120px 120px
      xl: ['3.5rem', '100%'], //56px 56px
      l: ['3rem', '125%'], //48px 60px
      m: ['2.25rem', '125%'], //36px 45px
      s: ['1.5rem', '124%'], //24px 30px
      xs: ['1.125rem', '100%'], //18px 18px
      xxs: ['0.875rem', '120%'], //14px 16.8px
      body: ['0.875rem', '157%'], //14px 22px
      button: ['1rem', '1.125rem'], //16px 18px
      'text-link': ['1rem', '1.125rem'], //16px 18px
      caption: ['1rem', '0.875rem'], //16px 14px
      small: ['0.75rem', '130%'], //12px 15.6px
      legal: ['0.75rem', '130%'], //12px 15.6px
      base: ['1rem', '1.125rem'], //16px 18px
    },
    colors: {
      'light-gray': '#F8F6F4',
      'dark-gray': '#686869',
      gray: '#C4BFB6',
      primary: 'red',
      darkprimary: 'orange',
      secondary: '#000000',
      white: '#FFFFFF',
      black: '#000000',
      transparent: 'transparent',
    },
    fontWeight: {
      demi: '500',
      heavy: '600',
      bold: '700',
      medium: '450',
      regular: '400',
      light: '300',
      extralight: '200',
    },
  },
};
//tailwind.secondary.js

module.exports = {
  name: 'secondary',
  extend: {
    fontSize: {
      xxl: ['3.5rem', '114%'], //56px 63.84px
      xl: ['3.5rem', '114%'], //56px 63.84px
      l: ['2rem', '125%'], //32px 40px
      m: ['1.5rem', '2rem'], //24px 32px
      s: ['1.25rem', '1.75rem'], //20px 28px
      xs: ['1rem', '1.25rem'], //16px 20px
      xxs: ['0.75rem', '1rem'], //12px 16px
      body: ['0.875rem', '157%'], //14px 21.98px
      'large-body': ['1.125rem', '133%'], //18px 23.94px
      button: ['0.875rem', '120%'], //14px 16.8px
      'text-link': ['0.875rem', '0.875rem'], //14px 14px
      small: ['0.75rem', '130%'], //12px 15.6px
      legal: ['0.625rem', '130%'], //10px 13px
      base: ['1rem', '1.125rem'], //16px 18px
    },
    colors: {
      'light-gray': '#F9F9F9',
      'dark-gray': '#54585A',
      gray: '#D2D1D0',
      primary: 'blue',
      darkprimary: 'yellow',
      secondary: '#000000',
      white: '#FFFFFF',
      black: '#000000',
      transparent: 'transparent',
      'light-black': '#454545',
    },
    fontWeight: {
      bold: '700',
      heavy: '600',
      'semi-bold': '600',
      medium: '500',
      regular: '400',
      'extra-light': '300',
    },
  },
};
Pay close attention while giving the theme name in configuration files because it’ll be considered while applying themes.

Export themes

Now that configuration files are all set up, Export them to use it. Create a index.js file that exports the themes. You can find this file in the same folder as other theme configuration files.

//index.js
const base = require('./tailwind.base');
const primary = require('./tailwind.primary');
const secondary = require('./tailwind.secondary');

module.exports = {
  base: base,
  themes: [primary, secondary],
};

Configure tailwind

In the previous step, we’ve exported the multiple themes that can be configured in Tailwind. It is time to configure the tailwind.config.js to apply themes in Tailwind.

First of all, set the base configuration file to setup the base theme object in theme key.

Now, Leverage the tailwindcss-themer plugin. This plugin expects an object with two keys.

  1. defaultTheme : Provide default theme as a fallback.

  2. themes : It expects an array of themes that are configured in previous steps.

Please refer to the code snippet below to understand it.

//tailwind.config.js
const app = require('./src/theme');

module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  plugins: [
    require('tailwindcss-themer')({
      defaultTheme: {
        extend: app.themes[0], //Provide default theme as a fallback
      },
      themes: app.themes, //Provide array of themes which are configured in previous steps.
    }),
  ],
  theme: app.base, //Provide base theme
};

By following the above steps, multiple themes are successfully configured.

Now that we've successfully configured the multi-theming, Please refer How to leverage multi-theming as a next step to leverage multi-theming in the app.