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

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

·

5 min read

In this article, we will explore the implementation of multiple themes within a single source code. Our approach to achieving multi-theming relies on React's context feature. To accomplish this, we encapsulate the entire application within the theme context.

This article assumes that theme configuration is already done. If not then please read to Configure multi-theming configure theme.

Initiate multi theming

Before delving into the practical application of multi-theming, there's a preliminary step we need to take.

Head over to src/Layout.tsx and import the useTheme hook we crafted in the ThemeContext.tsx file. Refer code snippet below to get more idea.

//Layout.tsx
import { useTheme } from 'lib/context/ThemeContext';

const Layout: React.FC<LayoutProps> = ({ layoutData }: LayoutProps): JSX.Element => {
  //...Rest of the code
  const { themeName } = useTheme();
  //...Rest of the code
}

useTheme will give us the themeName variable which represents the current theme for the given site.

We need to add the current theme name as a class to a wrapper div that encompasses the entire application. Refer to line #9 in code snippet below to get more idea.

//Layout.tsx
import { useTheme } from 'lib/context/ThemeContext';

const Layout: React.FC<LayoutProps> = ({ layoutData }: LayoutProps): JSX.Element => {
  //...Rest of the code
  const { themeName } = useTheme();
  //...Rest of the code

  return (
    <div className={classNames(mainClassPageEditing, themeName)}>
      <header>
        {/* Render placeholder for header */}
        <div id="header">{route && <Placeholder name="headless-header" rendering={route} />}</div>
      </header>
      <main>
        {/* Render placeholder for main content */}
        <div id="content">{route && <Placeholder name="headless-main" rendering={route} />}</div>
      </main>
      <footer>
        {/* Render placeholder for footer */}
        <div id="footer">{route && <Placeholder name="headless-footer" rendering={route} />}</div>
        <AlgoliaCrawler layoutData={layoutData} />
      </footer>
    </div>
  );
}
Typically we need to append the theme name as a class to the div whose children are intended to adopt the theme. In our scenario, it should be the div that encapsulates the entire application.
💡
If you want to dig more into how npm: tailwindcss-themer works, I recommend exploring it by consulting the documentation.

Steps to use multi-theming

Follow below steps to create a component which can use multi theme. We’ll create a component named with HeroTwoColumn in our example.

  1. Create 2 files one is component and second is theme file.

    • HeroTwoColumn.tsx

        //HeroTwoColumn.tsx
      
        import { useTheme } from 'lib/context/ThemeContext';
        import { HeroTwoColumnTheme } from './HeroTwoColumn.theme';
      
        export type HeroTwoColumnProps = Feature.EnterpriseWeb.Components.Hero.HeroTwoColumn;
      
        const HeroTwoColumn = (props: HeroTwoColumnProps) => {
          const { themeName, themeData } = useTheme(HeroTwoColumnTheme);
      
            if (!props.fields) return null;
      
            return (
              <Component variant="1g" dataComponent="hero/herotwocolumn" gap="!gap-xxxs md:gap-s" {...props}>
                {themeName === 'primary' && (
                  <div className="col-span-12 md:col-span-1">
                    <span className="inline-block h-[3px] w-1 bg-primary md:w-full"></span>
                  </div>
                )}
      
                <div className={`col-span-12 ${themeName === 'primary' ? 'md:col-span-5' : 'md:col-span-6'}`}>
                  <Headline useTag="h1" classes={themeData.classes.headlineClass} {...props} />
                </div>
      
                <div className="col-span-12 md:col-span-6">
                  <Subheadline useTag="h2" classes={themeData.classes.subheadlineClass} {...props} />
                  <BodyCopy classes={themeData.classes.bodyClass} {...props} />
                  <ButtonGroup classes={themeData.classes.buttonGroupClass} {...props} />
                </div>
              </Component>
          );
        }
      
    • HeroTwoColumn.theme.ts

        //HeroTwoColumn.theme.ts
      
        import { ThemeFile } from 'lib/context/ThemeContext';
      
        export const HeroTwoColumnTheme: ThemeFile = {
          primary: {
            classes: {
              headlineClass: 'text-sm-1 lg:text-1 font-heavy mb-s',
              subheadlineClass: 'text-sm-m 1g:text-m font-medium mb-s',
              bodyClass: 'text-body text-dark-gray font-regular mb-s',
              buttonGroupClass: {
                wrapper: 'flex-col',
                cta1Classes: 'mr-2 mb-m md:mb-0',
                cta2Classes: 'ml-xs md:ml-0 px-s md:px-0',
              },
            },
          },
          secondary: {
            classes: {
              headlineClass: 'text-sm-m lg:text-1 font-medium',
              subheadlineClass: 'text-sm-m 1g:text-1 font-extra-light',
              bodyClass: 'text-body text-dark-gray font-regular mb-xxs',
              buttonGroupClass: {
                wrapper: 'flex-col md:items-center',
                cta1Classes: 'mr-2',
                cta2Classes: 'my-s md:my-0',
              },
            },
          },
        };
      
  2. HeroTwoColumn contains of headline, subheadline, body and CTA elements. So, theme file contains classes as per the given elements in the component.

  3. Import useTheme context and pass the component’s theme object imported from theme file in it. Please refer line #7 in HeroTwoColumn.tsx.

  4. useTheme will return two variables -

    a. themeName - contains current theme name.

    b. themeData - contains the theme object from theme file.

  5. If current theme is primary, Component will be rendered using primary object from theme file.

  6. For example if we think about Headline, We’ve passed themeData.classes.headlineClass in order to style it. Please refer line #20 in HeroTwoColumn.tsx. If current theme is primary it’ll refer to headlineClass key at line #6. If current theme is secondary it’ll refer to headlineClass key at line #18 in HeroTwoColumn.theme.ts.

In summary, we've effectively implemented multi-theming in our JSS application by following above steps. By leveraging this feature, we've enhanced developer experience while ensuring adaptability and scalability for future improvements.

Extra notes

If you're utilizing the latest version of JSS, ensure to create the HeroTwoColumn.theme.tsx file outside the /src/components folder. JSS treats all files within /src/components as authorable components, and placing HeroTwoColumn.theme.ts inside it would register it as a component in the JSS ecosystem.
💡
In case if we require advanced theming, We can return the function instead of returning the direct theme object. Please refer below code.
//Sample theme file for advance theming

import classNames from 'classnames';
import { ThemeFile, ThemeName } from 'lib/context/ThemeContext';
import { BorderStyle, TextAlignment } from './SectionHeadline';

const primaryHeadlineClasses = 'text-s md:text-m font-heavy pb-xs mb-s';

const secondaryHeadlineClasses = 'text-m md:text-l font-extra-light pb-xs mb-s';

const getBorderClasses = (theme: ThemeName, borderStyle: BorderStyle) => {
  if (borderStyle !== 'default') {
    let borderClasses = theme === 'primary' ? 'border-b-6 ' : 'border-b-3 ';

    switch (borderStyle) {
      case 'dark':
        borderClasses += theme === 'primary' ? 'border-primary' : 'border-secondary';
        break;
      case 'light':
        borderClasses += theme === 'primary' ? 'border-light-gray' : 'border-gray';
        break;
      default:
        break;
    }

    return borderClasses;
  }
  return '';
};

const getDynamicStyles = (
  theme: ThemeName,
  alignment: TextAlignment,
  borderStyle: BorderStyle
): string => {
  return classNames(`text-${alignment}`, getBorderClasses(theme, borderStyle));
};

export const SectionHeadlineTheme = (
  alignment: TextAlignment,
  borderStyle: BorderStyle
): ThemeFile => {
  return {
    primary: {
      classes: {
        headlineContainer: classNames(
          primaryHeadlineClasses,
          getDynamicStyles('primary', alignment, borderStyle)
        ),
      },
    },
    secondary: {
      classes: {
        headlineContainer: classNames(
          secondaryHeadlineClasses,
          getDynamicStyles('secondary', alignment, borderStyle)
        ),
      },
    },
  };
};
Keep in mind if the component’s layout or theming is same across the design we can always use the original way of applying classes. i.e. Directly apply the style in the component itself.